// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/service.h" #include "include/dart_api.h" #include "platform/globals.h" #include "vm/compiler.h" #include "vm/coverage.h" #include "vm/cpu.h" #include "vm/dart_api_impl.h" #include "vm/dart_entry.h" #include "vm/debugger.h" #include "vm/isolate.h" #include "vm/lockers.h" #include "vm/message.h" #include "vm/message_handler.h" #include "vm/native_entry.h" #include "vm/native_arguments.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_service.h" #include "vm/reusable_handles.h" #include "vm/stack_frame.h" #include "vm/symbols.h" #include "vm/unicode.h" #include "vm/version.h" namespace dart { DEFINE_FLAG(bool, trace_service, false, "Trace VM service requests."); DEFINE_FLAG(bool, trace_service_pause_events, false, "Trace VM service isolate pause events."); DECLARE_FLAG(bool, enable_type_checks); DECLARE_FLAG(bool, enable_asserts); struct ResourcesEntry { const char* path_; const char* resource_; int length_; }; extern ResourcesEntry __service_resources_[]; class Resources { public: static const int kNoSuchInstance = -1; static int ResourceLookup(const char* path, const char** resource) { ResourcesEntry* table = ResourceTable(); for (int i = 0; table[i].path_ != NULL; i++) { const ResourcesEntry& entry = table[i]; if (strcmp(path, entry.path_) == 0) { *resource = entry.resource_; ASSERT(entry.length_ > 0); return entry.length_; } } return kNoSuchInstance; } static const char* Path(int idx) { ASSERT(idx >= 0); ResourcesEntry* entry = At(idx); if (entry == NULL) { return NULL; } ASSERT(entry->path_ != NULL); return entry->path_; } static int Length(int idx) { ASSERT(idx >= 0); ResourcesEntry* entry = At(idx); if (entry == NULL) { return kNoSuchInstance; } ASSERT(entry->path_ != NULL); return entry->length_; } static const uint8_t* Resource(int idx) { ASSERT(idx >= 0); ResourcesEntry* entry = At(idx); if (entry == NULL) { return NULL; } return reinterpret_cast(entry->resource_); } private: static ResourcesEntry* At(int idx) { ASSERT(idx >= 0); ResourcesEntry* table = ResourceTable(); for (int i = 0; table[i].path_ != NULL; i++) { if (idx == i) { return &table[i]; } } return NULL; } static ResourcesEntry* ResourceTable() { return &__service_resources_[0]; } DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(Resources); }; 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_; }; class LibraryCoverageFilter : public CoverageFilter { public: explicit LibraryCoverageFilter(const Library& lib) : lib_(lib) {} bool ShouldOutputCoverageFor(const Library& lib, const Script& script, const Class& cls, const Function& func) const { return lib.raw() == lib_.raw(); } private: const Library& lib_; }; class ScriptCoverageFilter : public CoverageFilter { public: explicit ScriptCoverageFilter(const Script& script) : script_(script) {} bool ShouldOutputCoverageFor(const Library& lib, const Script& script, const Class& cls, const Function& func) const { return script.raw() == script_.raw(); } private: const Script& script_; }; class ClassCoverageFilter : public CoverageFilter { public: explicit ClassCoverageFilter(const Class& cls) : cls_(cls) {} bool ShouldOutputCoverageFor(const Library& lib, const Script& script, const Class& cls, const Function& func) const { return cls.raw() == cls_.raw(); } private: const Class& cls_; }; class FunctionCoverageFilter : public CoverageFilter { public: explicit FunctionCoverageFilter(const Function& func) : func_(func) {} bool ShouldOutputCoverageFor(const Library& lib, const Script& script, const Class& cls, const Function& func) const { return func.raw() == func_.raw(); } private: const Function& func_; }; static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) { void* new_ptr = realloc(reinterpret_cast(ptr), new_size); return reinterpret_cast(new_ptr); } static void SendIsolateServiceMessage(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); Isolate* isolate = arguments->isolate(); StackZone zone(isolate); HANDLESCOPE(isolate); GET_NON_NULL_NATIVE_ARGUMENT(SendPort, sp, arguments->NativeArgAt(0)); GET_NON_NULL_NATIVE_ARGUMENT(Array, message, arguments->NativeArgAt(1)); // Set the type of the OOB message. message.SetAt(0, Smi::Handle(isolate, Smi::New(Message::kServiceOOBMsg))); // Serialize message. uint8_t* data = NULL; MessageWriter writer(&data, &allocator, false); writer.WriteMessage(message); // TODO(turnidge): Throw an exception when the return value is false? PortMap::PostMessage(new Message(sp.Id(), data, writer.BytesWritten(), Message::kOOBPriority)); } static void SendRootServiceMessage(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); Isolate* isolate = arguments->isolate(); StackZone zone(isolate); HANDLESCOPE(isolate); GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(0)); Service::HandleRootMessage(message); } class ScopeStopwatch : public ValueObject { public: explicit ScopeStopwatch(const char* name) : name_(name) { start_ = OS::GetCurrentTimeMicros(); } int64_t GetElapsed() const { int64_t end = OS::GetCurrentTimeMicros(); ASSERT(end >= start_); return end - start_; } ~ScopeStopwatch() { int64_t elapsed = GetElapsed(); OS::Print("[%" Pd "] %s took %" Pd64 " micros.\n", OS::ProcessId(), name_, elapsed); } private: const char* name_; int64_t start_; }; bool Service::IsRunning() { MonitorLocker ml(monitor_); return (service_port_ != ILLEGAL_PORT) && (service_isolate_ != NULL); } void Service::SetServicePort(Dart_Port port) { MonitorLocker ml(monitor_); service_port_ = port; } void Service::SetServiceIsolate(Isolate* isolate) { MonitorLocker ml(monitor_); service_isolate_ = isolate; if (service_isolate_ != NULL) { service_isolate_->is_service_isolate_ = true; } } bool Service::HasServiceIsolate() { MonitorLocker ml(monitor_); return service_isolate_ != NULL; } bool Service::IsServiceIsolate(Isolate* isolate) { MonitorLocker ml(monitor_); return isolate == service_isolate_; } Dart_Port Service::WaitForLoadPort() { MonitorLocker ml(monitor_); while (initializing_ && (load_port_ == ILLEGAL_PORT)) { ml.Wait(); } return load_port_; } Dart_Port Service::LoadPort() { MonitorLocker ml(monitor_); return load_port_; } void Service::SetLoadPort(Dart_Port port) { MonitorLocker ml(monitor_); load_port_ = port; } void Service::SetEventMask(uint32_t mask) { event_mask_ = mask; } static void SetEventMask(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); Isolate* isolate = arguments->isolate(); StackZone zone(isolate); HANDLESCOPE(isolate); GET_NON_NULL_NATIVE_ARGUMENT(Integer, mask, arguments->NativeArgAt(0)); Service::SetEventMask(mask.AsTruncatedUint32Value()); } // These must be kept in sync with service/constants.dart #define VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID 1 #define VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID 2 static RawArray* MakeServiceControlMessage(Dart_Port port_id, intptr_t code, const String& name) { const Array& list = Array::Handle(Array::New(4)); ASSERT(!list.IsNull()); const Integer& code_int = Integer::Handle(Integer::New(code)); const Integer& port_int = Integer::Handle(Integer::New(port_id)); const SendPort& send_port = SendPort::Handle(SendPort::New(port_id)); list.SetAt(0, code_int); list.SetAt(1, port_int); list.SetAt(2, send_port); list.SetAt(3, name); return list.raw(); } class RegisterRunningIsolatesVisitor : public IsolateVisitor { public: explicit RegisterRunningIsolatesVisitor(Isolate* service_isolate) : IsolateVisitor(), register_function_(Function::Handle(service_isolate)), service_isolate_(service_isolate) { ASSERT(Service::IsServiceIsolate(Isolate::Current())); // Get library. const String& library_url = Symbols::DartVMService(); ASSERT(!library_url.IsNull()); const Library& library = Library::Handle(Library::LookupLibrary(library_url)); ASSERT(!library.IsNull()); // Get function. const String& function_name = String::Handle(String::New("_registerIsolate")); ASSERT(!function_name.IsNull()); register_function_ = library.LookupFunctionAllowPrivate(function_name); ASSERT(!register_function_.IsNull()); } virtual void VisitIsolate(Isolate* isolate) { ASSERT(Service::IsServiceIsolate(Isolate::Current())); if (Service::IsServiceIsolate(isolate) || (isolate == Dart::vm_isolate())) { // We do not register the service or vm isolate. return; } // Setup arguments for call. Dart_Port port_id = isolate->main_port(); const Integer& port_int = Integer::Handle(Integer::New(port_id)); ASSERT(!port_int.IsNull()); const SendPort& send_port = SendPort::Handle(SendPort::New(port_id)); const String& name = String::Handle(String::New(isolate->name())); ASSERT(!name.IsNull()); const Array& args = Array::Handle(Array::New(3)); ASSERT(!args.IsNull()); args.SetAt(0, port_int); args.SetAt(1, send_port); args.SetAt(2, name); Object& r = Object::Handle(service_isolate_); r = DartEntry::InvokeFunction(register_function_, args); if (FLAG_trace_service) { OS::Print("vm-service: Isolate %s %" Pd64 " registered.\n", name.ToCString(), port_id); } ASSERT(!r.IsError()); } private: Function& register_function_; Isolate* service_isolate_; }; static Dart_Port ExtractPort(Isolate* isolate, Dart_Handle receivePort) { const ReceivePort& rp = Api::UnwrapReceivePortHandle(isolate, receivePort); if (rp.IsNull()) { return ILLEGAL_PORT; } return rp.Id(); } static void OnStart(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); Isolate* isolate = arguments->isolate(); StackZone zone(isolate); HANDLESCOPE(isolate); { if (FLAG_trace_service) { OS::Print("vm-service: Booting dart:vmservice library.\n"); } // Boot the dart:vmservice library. Dart_EnterScope(); Dart_Handle url_str = Dart_NewStringFromCString(Symbols::Name(Symbols::kDartVMServiceId)); Dart_Handle library = Dart_LookupLibrary(url_str); ASSERT(Dart_IsLibrary(library)); Dart_Handle result = Dart_Invoke(library, Dart_NewStringFromCString("boot"), 0, NULL); ASSERT(!Dart_IsError(result)); Dart_Port port = ExtractPort(isolate, result); ASSERT(port != ILLEGAL_PORT); Service::SetServicePort(port); Dart_ExitScope(); } { if (FLAG_trace_service) { OS::Print("vm-service: Registering running isolates.\n"); } // Register running isolates with service. RegisterRunningIsolatesVisitor register_isolates(isolate); Isolate::VisitIsolates(®ister_isolates); } } struct VmServiceNativeEntry { const char* name; int num_arguments; Dart_NativeFunction function; }; static VmServiceNativeEntry _VmServiceNativeEntries[] = { {"VMService_SendIsolateServiceMessage", 2, SendIsolateServiceMessage}, {"VMService_SendRootServiceMessage", 1, SendRootServiceMessage}, {"VMService_SetEventMask", 1, SetEventMask}, {"VMService_OnStart", 0, OnStart }, }; static Dart_NativeFunction VmServiceNativeResolver(Dart_Handle name, int num_arguments, bool* auto_setup_scope) { const Object& obj = Object::Handle(Api::UnwrapHandle(name)); if (!obj.IsString()) { return NULL; } const char* function_name = obj.ToCString(); ASSERT(function_name != NULL); ASSERT(auto_setup_scope != NULL); *auto_setup_scope = true; intptr_t n = sizeof(_VmServiceNativeEntries) / sizeof(_VmServiceNativeEntries[0]); for (intptr_t i = 0; i < n; i++) { VmServiceNativeEntry entry = _VmServiceNativeEntries[i]; if ((strcmp(function_name, entry.name) == 0) && (num_arguments == entry.num_arguments)) { return entry.function; } } return NULL; } const char* Service::kIsolateName = "vm-service"; EmbedderServiceHandler* Service::isolate_service_handler_head_ = NULL; EmbedderServiceHandler* Service::root_service_handler_head_ = NULL; Isolate* Service::service_isolate_ = NULL; Dart_Port Service::service_port_ = ILLEGAL_PORT; Dart_Port Service::load_port_ = ILLEGAL_PORT; Dart_IsolateCreateCallback Service::create_callback_ = NULL; Monitor* Service::monitor_ = NULL; bool Service::initializing_ = true; uint32_t Service::event_mask_ = 0; bool Service::IsServiceIsolateName(const char* name) { ASSERT(name != NULL); return strcmp(name, kIsolateName) == 0; } bool Service::SendIsolateStartupMessage() { if (!IsRunning()) { return false; } Isolate* isolate = Isolate::Current(); if (IsServiceIsolate(isolate)) { return false; } ASSERT(isolate != NULL); HANDLESCOPE(isolate); const String& name = String::Handle(String::New(isolate->name())); ASSERT(!name.IsNull()); const Array& list = Array::Handle( MakeServiceControlMessage(Dart_GetMainPortId(), VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID, name)); ASSERT(!list.IsNull()); uint8_t* data = NULL; MessageWriter writer(&data, &allocator, false); writer.WriteMessage(list); intptr_t len = writer.BytesWritten(); if (FLAG_trace_service) { OS::Print("vm-service: Isolate %s %" Pd64 " registered.\n", name.ToCString(), Dart_GetMainPortId()); } return PortMap::PostMessage( new Message(service_port_, data, len, Message::kNormalPriority)); } bool Service::SendIsolateShutdownMessage() { if (!IsRunning()) { return false; } Isolate* isolate = Isolate::Current(); if (IsServiceIsolate(isolate)) { return false; } ASSERT(isolate != NULL); HANDLESCOPE(isolate); const String& name = String::Handle(String::New(isolate->name())); ASSERT(!name.IsNull()); const Array& list = Array::Handle( MakeServiceControlMessage(Dart_GetMainPortId(), VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID, name)); ASSERT(!list.IsNull()); uint8_t* data = NULL; MessageWriter writer(&data, &allocator, false); writer.WriteMessage(list); intptr_t len = writer.BytesWritten(); if (FLAG_trace_service) { OS::Print("vm-service: Isolate %s %" Pd64 " deregistered.\n", name.ToCString(), Dart_GetMainPortId()); } return PortMap::PostMessage( new Message(service_port_, data, len, Message::kNormalPriority)); } Dart_Handle Service::GetSource(const char* name) { ASSERT(name != NULL); int i = 0; while (true) { const char* path = Resources::Path(i); if (path == NULL) { break; } ASSERT(*path != '\0'); // Skip the '/'. path++; if (strcmp(name, path) == 0) { const uint8_t* str = Resources::Resource(i); intptr_t length = Resources::Length(i); return Dart_NewStringFromUTF8(str, length); } i++; } return Dart_Null(); } Dart_Handle Service::LibraryTagHandler(Dart_LibraryTag tag, Dart_Handle library, Dart_Handle url) { if (tag == Dart_kCanonicalizeUrl) { // url is already canonicalized. return url; } if (tag != Dart_kSourceTag) { FATAL("Service::LibraryTagHandler encountered an unexpected tag."); } ASSERT(tag == Dart_kSourceTag); const char* url_string = NULL; Dart_Handle result = Dart_StringToCString(url, &url_string); if (Dart_IsError(result)) { return result; } Dart_Handle source = GetSource(url_string); if (Dart_IsError(source)) { return source; } return Dart_LoadSource(library, url, source, 0, 0); } void Service::MaybeInjectVMServiceLibrary(Isolate* isolate) { ASSERT(isolate != NULL); ASSERT(isolate->name() != NULL); if (!Service::IsServiceIsolateName(isolate->name())) { // Not service isolate. return; } if (HasServiceIsolate()) { // Service isolate already exists. return; } SetServiceIsolate(isolate); StackZone zone(isolate); HANDLESCOPE(isolate); // Register dart:vmservice library. const String& url_str = String::Handle(Symbols::DartVMService().raw()); const Library& library = Library::Handle(Library::New(url_str)); library.Register(); library.set_native_entry_resolver(VmServiceNativeResolver); // Temporarily install our library tag handler. isolate->set_library_tag_handler(LibraryTagHandler); // Get script source. const char* resource = NULL; const char* path = "/vmservice.dart"; intptr_t r = Resources::ResourceLookup(path, &resource); ASSERT(r != Resources::kNoSuchInstance); ASSERT(resource != NULL); const String& source_str = String::Handle( String::FromUTF8(reinterpret_cast(resource), r)); ASSERT(!source_str.IsNull()); const Script& script = Script::Handle( isolate, Script::New(url_str, source_str, RawScript::kLibraryTag)); // Compile script. Dart_EnterScope(); // Need to enter scope for tag handler. library.SetLoadInProgress(); const Error& error = Error::Handle(isolate, Compiler::Compile(library, script)); ASSERT(error.IsNull()); Dart_Handle result = Dart_FinalizeLoading(false); ASSERT(!Dart_IsError(result)); Dart_ExitScope(); // Uninstall our library tag handler. isolate->set_library_tag_handler(NULL); } void Service::FinishedInitializing() { MonitorLocker ml(monitor_); initializing_ = false; ml.NotifyAll(); } static void ShutdownIsolate(uword parameter) { Isolate* isolate = reinterpret_cast(parameter); ASSERT(Service::IsServiceIsolate(isolate)); { // Print the error if there is one. This may execute dart code to // print the exception object, so we need to use a StartIsolateScope. StartIsolateScope start_scope(isolate); StackZone zone(isolate); HandleScope handle_scope(isolate); Error& error = Error::Handle(); error = isolate->object_store()->sticky_error(); if (!error.IsNull()) { OS::PrintErr("vm-service: Error: %s\n", error.ToErrorCString()); } Dart::RunShutdownCallback(); } { // Shut the isolate down. SwitchIsolateScope switch_scope(isolate); Dart::ShutdownIsolate(); } Service::SetServiceIsolate(NULL); Service::SetServicePort(ILLEGAL_PORT); if (FLAG_trace_service) { OS::Print("vm-service: Shutdown.\n"); } } class RunServiceTask : public ThreadPool::Task { public: virtual void Run() { ASSERT(Isolate::Current() == NULL); char* error = NULL; Isolate* isolate = NULL; Dart_IsolateCreateCallback create_callback = Service::create_callback(); // TODO(johnmccutchan): Support starting up service isolate without embedder // provided isolate creation callback. if (create_callback == NULL) { Service::FinishedInitializing(); return; } isolate = reinterpret_cast(create_callback(Service::kIsolateName, NULL, NULL, NULL, &error)); if (isolate == NULL) { OS::PrintErr("vm-service: Isolate creation error: %s\n", error); Service::FinishedInitializing(); return; } Isolate::SetCurrent(NULL); RunMain(isolate); Service::FinishedInitializing(); isolate->message_handler()->Run(Dart::thread_pool(), NULL, ShutdownIsolate, reinterpret_cast(isolate)); } protected: void RunMain(Isolate* isolate) { StartIsolateScope iso_scope(isolate); StackZone zone(isolate); HANDLESCOPE(isolate); // Invoke main which will return the loadScriptPort. const Library& root_library = Library::Handle(isolate, isolate->object_store()->root_library()); if (root_library.IsNull()) { if (FLAG_trace_service) { OS::Print("vm-service: Embedder did not install a script."); } // Service isolate is not supported by embedder. return; } ASSERT(!root_library.IsNull()); const String& entry_name = String::Handle(isolate, String::New("main")); ASSERT(!entry_name.IsNull()); const Function& entry = Function::Handle(isolate, root_library.LookupFunctionAllowPrivate(entry_name)); if (entry.IsNull()) { // Service isolate is not supported by embedder. if (FLAG_trace_service) { OS::Print("vm-service: Embedder did not provide a main function."); } return; } ASSERT(!entry.IsNull()); const Object& result = Object::Handle(isolate, DartEntry::InvokeFunction(entry, Object::empty_array())); ASSERT(!result.IsNull()); if (result.IsError()) { // Service isolate did not initialize properly. if (FLAG_trace_service) { const Error& error = Error::Cast(result); OS::Print("vm-service: Calling main resulted in an error: %s", error.ToErrorCString()); } return; } ASSERT(result.IsReceivePort()); const ReceivePort& rp = ReceivePort::Cast(result); Service::SetLoadPort(rp.Id()); } }; void Service::RunService() { ASSERT(monitor_ == NULL); monitor_ = new Monitor(); ASSERT(monitor_ != NULL); // Grab the isolate create callback here to avoid race conditions with tests // that change this after Dart_Initialize returns. create_callback_ = Isolate::CreateCallback(); Dart::thread_pool()->Run(new RunServiceTask()); } // A handler for a per-isolate request. // // If a handler returns true, the reply is complete and ready to be // posted. If a handler returns false, then it is responsible for // posting the reply (this can be used for asynchronous delegation of // the response handling). typedef bool (*IsolateMessageHandler)(Isolate* isolate, JSONStream* stream); struct IsolateMessageHandlerEntry { const char* method; IsolateMessageHandler handler; }; static IsolateMessageHandler FindIsolateMessageHandler(const char* method); // A handler for a root (vm-global) request. // // If a handler returns true, the reply is complete and ready to be // posted. If a handler returns false, then it is responsible for // posting the reply (this can be used for asynchronous delegation of // the response handling). typedef bool (*RootMessageHandler)(JSONStream* stream); struct RootMessageHandlerEntry { const char* method; RootMessageHandler handler; }; static RootMessageHandler FindRootMessageHandler(const char* method); static void PrintRequest(const JSONObject& obj, JSONStream* js) { JSONObject jsobj(&obj, "request"); jsobj.AddProperty("method", js->method()); { JSONArray jsarr(&jsobj, "param_keys"); for (intptr_t i = 0; i < js->num_params(); i++) { jsarr.AddValue(js->GetParamKey(i)); } } { JSONArray jsarr(&jsobj, "param_values"); for (intptr_t i = 0; i < js->num_params(); i++) { jsarr.AddValue(js->GetParamValue(i)); } } } static void PrintError(JSONStream* js, const char* format, ...) { Isolate* isolate = Isolate::Current(); va_list args; va_start(args, format); intptr_t len = OS::VSNPrint(NULL, 0, format, args); va_end(args); char* buffer = isolate->current_zone()->Alloc(len + 1); va_list args2; va_start(args2, format); OS::VSNPrint(buffer, (len + 1), format, args2); va_end(args2); JSONObject jsobj(js); jsobj.AddProperty("type", "Error"); jsobj.AddProperty("message", buffer); PrintRequest(jsobj, js); } static void PrintMissingParamError(JSONStream* js, const char* param) { PrintError(js, "%s expects the '%s' parameter", js->method(), param); } static void PrintInvalidParamError(JSONStream* js, const char* param) { PrintError(js, "%s: invalid '%s' parameter: %s", js->method(), param, js->LookupParam(param)); } static void PrintErrorWithKind(JSONStream* js, const char* kind, const char* format, ...) { Isolate* isolate = Isolate::Current(); va_list args; va_start(args, format); intptr_t len = OS::VSNPrint(NULL, 0, format, args); va_end(args); char* buffer = isolate->current_zone()->Alloc(len + 1); va_list args2; va_start(args2, format); OS::VSNPrint(buffer, (len + 1), format, args2); va_end(args2); JSONObject jsobj(js); jsobj.AddProperty("type", "Error"); jsobj.AddProperty("id", ""); jsobj.AddProperty("kind", kind); jsobj.AddProperty("message", buffer); PrintRequest(jsobj, js); } void Service::HandleIsolateMessage(Isolate* isolate, const Array& msg) { ASSERT(isolate != NULL); ASSERT(!msg.IsNull()); ASSERT(msg.Length() == 5); { StackZone zone(isolate); HANDLESCOPE(isolate); Instance& reply_port = Instance::Handle(isolate); String& method = String::Handle(isolate); Array& param_keys = Array::Handle(isolate); Array& param_values = Array::Handle(isolate); reply_port ^= msg.At(1); method ^= msg.At(2); param_keys ^= msg.At(3); param_values ^= msg.At(4); ASSERT(!method.IsNull()); ASSERT(!param_keys.IsNull()); ASSERT(!param_values.IsNull()); ASSERT(param_keys.Length() == param_values.Length()); if (!reply_port.IsSendPort()) { FATAL("SendPort expected."); } IsolateMessageHandler handler = FindIsolateMessageHandler(method.ToCString()); { JSONStream js; js.Setup(zone.GetZone(), SendPort::Cast(reply_port).Id(), method, param_keys, param_values); if (handler == NULL) { // Check for an embedder handler. EmbedderServiceHandler* e_handler = FindIsolateEmbedderHandler(method.ToCString()); if (e_handler != NULL) { EmbedderHandleMessage(e_handler, &js); } else { if (FindRootMessageHandler(method.ToCString()) != NULL) { PrintError(&js, "%s does not expect the 'isolateId' parameter", method.ToCString()); } else { PrintError(&js, "Unrecognized method: %s", method.ToCString()); } } js.PostReply(); } else { if (handler(isolate, &js)) { // Handler returns true if the reply is ready to be posted. // TODO(johnmccutchan): Support asynchronous replies. js.PostReply(); } } } } } static bool HandleIsolate(Isolate* isolate, JSONStream* js) { isolate->PrintJSON(js, false); return true; } static bool HandleIsolateGetStack(Isolate* isolate, JSONStream* js) { DebuggerStackTrace* stack = isolate->debugger()->StackTrace(); 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); // TODO(turnidge): Implement depth differently -- differentiate // inlined frames. jsobj.AddProperty("depth", i); } 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("type", "ServiceEvent"); jsobj.AddProperty("eventType", "_Echo"); jsobj.AddProperty("isolate", isolate); if (text != NULL) { jsobj.AddProperty("text", text); } } const String& message = String::Handle(String::New(js.ToCString())); uint8_t data[] = {0, 128, 255}; // TODO(koda): Add 'testing' event family. SendEvent(kEventFamilyDebug, message, data, sizeof(data)); } static bool HandleIsolateTriggerEchoEvent(Isolate* isolate, JSONStream* js) { Service::SendEchoEvent(isolate, js->LookupParam("text")); JSONObject jsobj(js); return HandleCommonEcho(&jsobj, js); } static bool HandleIsolateEcho(Isolate* isolate, JSONStream* js) { JSONObject jsobj(js); return HandleCommonEcho(&jsobj, js); } 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; r = strtol(s, &end_ptr, base); 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; r = strtoul(s, &end_ptr, base); 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; } 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(Isolate* isolate, 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(isolate, 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(); } else if (strcmp(arg, "not-initialized") == 0) { return Object::sentinel().raw(); } else if (strcmp(arg, "being-initialized") == 0) { return Object::transition_sentinel().raw(); } ObjectIdRing* ring = 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()); intptr_t id = 0; if (!GetIntegerId(parts[1], &id)) { return Object::sentinel().raw(); } if ((id < 0) || (id >= libs.Length())) { return Object::sentinel().raw(); } Library& lib = Library::Handle(); lib ^= libs.At(id); ASSERT(!lib.IsNull()); if (num_parts == 2) { return lib.raw(); } if (strcmp(parts[2], "scripts") == 0) { // Script ids look like "libraries/35/scripts/library%2Furl.dart" if (num_parts != 4) { 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)); 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)) { return script.raw(); } } } // Not found. return Object::sentinel().raw(); } static RawObject* LookupHeapObjectClasses(Isolate* isolate, char** parts, int num_parts) { // Class ids look like: "classes/17" if (num_parts < 2) { return Object::sentinel().raw(); } ClassTable* table = isolate->class_table(); intptr_t id; if (!GetIntegerId(parts[1], &id) || !table->IsValidIndex(id)) { return Object::sentinel().raw(); } Class& cls = Class::Handle(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(); func ^= cls.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/11" if (num_parts != 4) { return Object::sentinel().raw(); } intptr_t id; if (!GetIntegerId(parts[3], &id)) { return Object::sentinel().raw(); } Field& field = Field::Handle(cls.FieldFromIndex(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/11" if (num_parts != 4) { return Object::sentinel().raw(); } const char* encoded_id = parts[3]; String& id = String::Handle(isolate, String::New(encoded_id)); id = String::DecodeIRI(id); if (id.IsNull()) { return Object::sentinel().raw(); } Function& func = Function::Handle(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(); 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(); 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(); } Type& type = Type::Handle(); type ^= cls.CanonicalTypeFromIndex(id); if (type.IsNull()) { return Object::sentinel().raw(); } return type.raw(); } // Not found. return Object::sentinel().raw(); } static RawObject* LookupHeapObjectTypeArguments(Isolate* isolate, char** parts, int num_parts) { // 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(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* kCollectedPrefix = "collected-"; static intptr_t kCollectedPrefixLen = strlen(kCollectedPrefix); static const char* kNativePrefix = "native-"; static intptr_t kNativePrefixLen = strlen(kNativePrefix); static const char* kReusedPrefix = "reused-"; static 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(); } // Not found. return Object::sentinel().raw(); } static RawObject* LookupHeapObject(Isolate* isolate, const char* id_original, ObjectIdRing::LookupResult* result) { char* id = isolate->current_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; } if (strcmp(parts[0], "objects") == 0) { // Object ids look like "objects/1123" Object& obj = Object::Handle(isolate); ObjectIdRing::LookupResult lookup_result; obj = LookupObjectId(isolate, 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(isolate, parts, num_parts); } else if (strcmp(parts[0], "typearguments") == 0) { return LookupHeapObjectTypeArguments(isolate, parts, num_parts); } else if (strcmp(parts[0], "code") == 0) { return LookupHeapObjectCode(isolate, parts, num_parts); } // Not found. return Object::sentinel().raw(); } static void PrintSentinel(JSONStream* js, const char* id, const char* preview) { JSONObject jsobj(js); jsobj.AddProperty("type", "Sentinel"); jsobj.AddProperty("id", id); jsobj.AddProperty("valueAsString", preview); } static SourceBreakpoint* LookupBreakpoint(Isolate* isolate, const char* id) { size_t end_pos = strcspn(id, "/"); const char* rest = NULL; if (end_pos < strlen(id)) { rest = id + end_pos + 1; // +1 for '/'. } if (strncmp("breakpoints", id, end_pos) == 0) { if (rest == NULL) { return NULL; } intptr_t bpt_id = 0; SourceBreakpoint* bpt = NULL; if (GetIntegerId(rest, &bpt_id)) { bpt = isolate->debugger()->GetBreakpointById(bpt_id); } return bpt; } return NULL; } static bool PrintInboundReferences(Isolate* isolate, Object* target, intptr_t limit, JSONStream* js) { ObjectGraph graph(isolate); 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); jselement.AddProperty("slot", ""); if (source.IsArray()) { intptr_t element_index = slot_offset.Value() - (Array::element_offset(0) >> kWordSizeLog2); jselement.AddProperty("slot", 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("slot", field); } } // We nil out the array after generating the response to prevent // reporting suprious references when repeatedly looking for the // references to an object. path.SetAt(i * 2, Object::null_object()); } } return true; } static bool HandleIsolateGetInboundReferences(Isolate* isolate, 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 (target_id == NULL) { PrintMissingParamError(js, "limit"); return true; } intptr_t limit; if (!GetIntegerId(limit_cstr, &limit)) { PrintInvalidParamError(js, "limit"); return true; } Object& obj = Object::Handle(isolate); ObjectIdRing::LookupResult lookup_result; { HANDLESCOPE(isolate); obj = LookupHeapObject(isolate, target_id, &lookup_result); } if (obj.raw() == Object::sentinel().raw()) { if (lookup_result == ObjectIdRing::kCollected) { PrintErrorWithKind( js, "InboundReferencesCollected", "attempt to find a retaining path for a collected object\n"); return true; } else if (lookup_result == ObjectIdRing::kExpired) { PrintErrorWithKind( js, "InboundReferencesExpired", "attempt to find a retaining path for an expired object\n"); return true; } PrintInvalidParamError(js, "targetId"); return true; } return PrintInboundReferences(isolate, &obj, limit, js); } static bool PrintRetainingPath(Isolate* isolate, Object* obj, intptr_t limit, JSONStream* js) { ObjectGraph graph(isolate); 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(); Object& parent = Object::Handle(); Smi& offset_from_parent = Smi::Handle(); Class& parent_class = Class::Handle(); Array& parent_field_map = 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("index", i); jselement.AddProperty("value", element); // Interpret the word offset from parent as list index or instance field. // TODO(koda): User-friendly interpretation for map entries. offset_from_parent ^= path.At((i * 2) + 1); int parent_i = i + 1; if (parent_i < limit) { parent = path.At(parent_i * 2); if (parent.IsArray()) { intptr_t element_index = offset_from_parent.Value() - (Array::element_offset(0) >> kWordSizeLog2); jselement.AddProperty("parentListIndex", element_index); } else if (parent.IsInstance()) { parent_class ^= parent.clazz(); parent_field_map = parent_class.OffsetToFieldMap(); intptr_t offset = offset_from_parent.Value(); if (offset > 0 && offset < parent_field_map.Length()) { field ^= parent_field_map.At(offset); jselement.AddProperty("parentField", field); } } } } // 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 < limit; ++i) { path.SetAt(i * 2, Object::null_object()); } return true; } static bool HandleIsolateGetRetainingPath(Isolate* isolate, 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 (target_id == NULL) { PrintMissingParamError(js, "limit"); return true; } intptr_t limit; if (!GetIntegerId(limit_cstr, &limit)) { PrintInvalidParamError(js, "limit"); return true; } Object& obj = Object::Handle(isolate); ObjectIdRing::LookupResult lookup_result; { HANDLESCOPE(isolate); obj = LookupHeapObject(isolate, target_id, &lookup_result); } if (obj.raw() == Object::sentinel().raw()) { if (lookup_result == ObjectIdRing::kCollected) { PrintErrorWithKind( js, "RetainingPathCollected", "attempt to find a retaining path for a collected object\n"); return true; } else if (lookup_result == ObjectIdRing::kExpired) { PrintErrorWithKind( js, "RetainingPathExpired", "attempt to find a retaining path for an expired object\n"); return true; } PrintInvalidParamError(js, "targetId"); return true; } return PrintRetainingPath(isolate, &obj, limit, js); } static bool HandleIsolateGetRetainedSize(Isolate* isolate, JSONStream* js) { const char* target_id = js->LookupParam("targetId"); if (target_id == NULL) { PrintMissingParamError(js, "targetId"); return true; } ObjectIdRing::LookupResult lookup_result; Object& obj = Object::Handle(LookupHeapObject(isolate, target_id, &lookup_result)); if (obj.raw() == Object::sentinel().raw()) { if (lookup_result == ObjectIdRing::kCollected) { PrintErrorWithKind( js, "RetainedCollected", "attempt to calculate size retained by a collected object\n"); return true; } else if (lookup_result == ObjectIdRing::kExpired) { PrintErrorWithKind( js, "RetainedExpired", "attempt to calculate size retained by an expired object\n"); return true; } PrintInvalidParamError(js, "targetId"); return true; } if (obj.IsClass()) { const Class& cls = Class::Cast(obj); ObjectGraph graph(isolate); intptr_t retained_size = graph.SizeRetainedByClass(cls.id()); const Object& result = Object::Handle(Integer::New(retained_size)); result.PrintJSON(js, true); return true; } if (obj.IsInstance() || obj.IsNull()) { // We don't use Instance::Cast here because it doesn't allow null. ObjectGraph graph(isolate); intptr_t retained_size = graph.SizeRetainedByInstance(obj); const Object& result = Object::Handle(Integer::New(retained_size)); result.PrintJSON(js, true); return true; } PrintError(js, "%s: Invalid 'targetId' parameter value: " "id '%s' does not correspond to a " "library, class, or instance", js->method(), target_id); return true; } static bool HandleIsolateEval(Isolate* isolate, JSONStream* js) { const char* target_id = js->LookupParam("targetId"); if (target_id == NULL) { PrintMissingParamError(js, "targetId"); return true; } const char* expr = js->LookupParam("expression"); if (expr == NULL) { PrintMissingParamError(js, "expression"); return true; } const String& expr_str = String::Handle(isolate, String::New(expr)); ObjectIdRing::LookupResult lookup_result; Object& obj = Object::Handle(LookupHeapObject(isolate, target_id, &lookup_result)); if (obj.raw() == Object::sentinel().raw()) { if (lookup_result == ObjectIdRing::kCollected) { PrintSentinel(js, "objects/collected", ""); } else if (lookup_result == ObjectIdRing::kExpired) { PrintSentinel(js, "objects/expired", ""); } else { PrintInvalidParamError(js, "targetId"); } return true; } if (obj.IsLibrary()) { const Library& lib = Library::Cast(obj); const Object& result = Object::Handle(lib.Evaluate(expr_str, Array::empty_array(), Array::empty_array())); result.PrintJSON(js, true); return true; } if (obj.IsClass()) { const Class& cls = Class::Cast(obj); const Object& result = Object::Handle(cls.Evaluate(expr_str, Array::empty_array(), Array::empty_array())); 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(isolate); instance ^= obj.raw(); const Object& result = Object::Handle(instance.Evaluate(expr_str, Array::empty_array(), Array::empty_array())); result.PrintJSON(js, true); return true; } PrintError(js, "%s: Invalid 'targetId' parameter value: " "id '%s' does not correspond to a " "library, class, or instance", js->method(), target_id); return true; } class GetInstancesVisitor : public ObjectGraph::Visitor { public: GetInstancesVisitor(const Class& cls, const Array& storage) : cls_(cls), storage_(storage), count_(0) {} virtual Direction VisitObject(ObjectGraph::StackIterator* it) { RawObject* raw_obj = it->Get(); if (raw_obj->IsFreeListElement()) { return kProceed; } Isolate* isolate = Isolate::Current(); REUSABLE_OBJECT_HANDLESCOPE(isolate); Object& obj = isolate->ObjectHandle(); obj = raw_obj; if (obj.GetClassId() == cls_.id()) { if (!storage_.IsNull() && count_ < storage_.Length()) { storage_.SetAt(count_, obj); } ++count_; } return kProceed; } intptr_t count() const { return count_; } private: const Class& cls_; const Array& storage_; intptr_t count_; }; static bool HandleIsolateGetInstances(Isolate* isolate, JSONStream* js) { const char* target_id = js->LookupParam("classId"); if (target_id == NULL) { PrintMissingParamError(js, "classId"); return true; } const char* limit_cstr = js->LookupParam("limit"); if (target_id == 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(isolate, target_id, NULL)); if (obj.raw() == Object::sentinel().raw() || !obj.IsClass()) { PrintInvalidParamError(js, "classId"); return true; } const Class& cls = Class::Cast(obj); Array& storage = Array::Handle(Array::New(limit)); GetInstancesVisitor visitor(cls, storage); ObjectGraph graph(isolate); graph.IterateObjects(&visitor); intptr_t count = visitor.count(); if (count < limit) { // Truncate the list using utility method for GrowableObjectArray. GrowableObjectArray& wrapper = GrowableObjectArray::Handle( GrowableObjectArray::New(storage)); wrapper.SetLength(count); storage = Array::MakeArray(wrapper); } JSONObject jsobj(js); jsobj.AddProperty("type", "InstanceSet"); jsobj.AddProperty("id", "instance_set"); jsobj.AddProperty("totalCount", count); jsobj.AddProperty("sampleCount", storage.Length()); jsobj.AddProperty("sample", storage); return true; } static bool HandleIsolateGetCoverage(Isolate* isolate, JSONStream* js) { if (!js->HasParam("targetId")) { CodeCoverage::PrintJSON(isolate, js, NULL); return true; } const char* target_id = js->LookupParam("targetId"); Object& obj = Object::Handle(LookupHeapObject(isolate, target_id, NULL)); if (obj.raw() == Object::sentinel().raw()) { PrintInvalidParamError(js, "targetId"); return true; } if (obj.IsScript()) { ScriptCoverageFilter sf(Script::Cast(obj)); CodeCoverage::PrintJSON(isolate, js, &sf); return true; } if (obj.IsLibrary()) { LibraryCoverageFilter lf(Library::Cast(obj)); CodeCoverage::PrintJSON(isolate, js, &lf); return true; } if (obj.IsClass()) { ClassCoverageFilter cf(Class::Cast(obj)); CodeCoverage::PrintJSON(isolate, js, &cf); return true; } if (obj.IsFunction()) { FunctionCoverageFilter ff(Function::Cast(obj)); CodeCoverage::PrintJSON(isolate, js, &ff); return true; } PrintError(js, "%s: Invalid 'targetId' parameter value: " "id '%s' does not correspond to a " "script, library, class, or function", js->method(), target_id); return true; } static bool HandleIsolateAddBreakpoint(Isolate* isolate, JSONStream* js) { if (!js->HasParam("line")) { PrintMissingParamError(js, "line"); return true; } const char* line_param = js->LookupParam("line"); intptr_t line = -1; if (!GetIntegerId(line_param, &line)) { PrintInvalidParamError(js, "line"); return true; } const char* script_id = js->LookupParam("script"); Object& obj = Object::Handle(LookupHeapObject(isolate, script_id, NULL)); if (obj.raw() == Object::sentinel().raw() || !obj.IsScript()) { PrintInvalidParamError(js, "script"); return true; } const Script& script = Script::Cast(obj); const String& script_url = String::Handle(script.url()); SourceBreakpoint* bpt = isolate->debugger()->SetBreakpointAtLine(script_url, line); if (bpt == NULL) { PrintError(js, "Unable to set breakpoint at line %s", line_param); return true; } bpt->PrintJSON(js); return true; } static bool HandleIsolateRemoveBreakpoint(Isolate* isolate, JSONStream* js) { if (!js->HasParam("breakpointId")) { PrintMissingParamError(js, "breakpointId"); return true; } const char* bpt_id = js->LookupParam("breakpointId"); SourceBreakpoint* bpt = LookupBreakpoint(isolate, bpt_id); if (bpt == NULL) { fprintf(stderr, "ERROR1"); PrintInvalidParamError(js, "breakpointId"); return true; } isolate->debugger()->RemoveBreakpoint(bpt->id()); fprintf(stderr, "SUCCESS"); // TODO(turnidge): Consider whether the 'Success' type is proper. JSONObject jsobj(js); jsobj.AddProperty("type", "Success"); jsobj.AddProperty("id", ""); return true; } static RawClass* GetMetricsClass(Isolate* isolate) { const Library& prof_lib = Library::Handle(isolate, Library::ProfilerLibrary()); ASSERT(!prof_lib.IsNull()); const String& metrics_cls_name = String::Handle(isolate, String::New("Metrics")); ASSERT(!metrics_cls_name.IsNull()); const Class& metrics_cls = Class::Handle(isolate, prof_lib.LookupClass(metrics_cls_name)); ASSERT(!metrics_cls.IsNull()); return metrics_cls.raw(); } static bool HandleNativeMetricsList(Isolate* isolate, JSONStream* js) { JSONObject obj(js); obj.AddProperty("type", "MetricList"); { JSONArray metrics(&obj, "metrics"); Metric* current = isolate->metrics_list_head(); while (current != NULL) { metrics.AddValue(current); current = current->next(); } } return true; } static bool HandleNativeMetric(Isolate* isolate, JSONStream* js, const char* id) { Metric* current = 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(); } PrintError(js, "Native Metric %s not found\n", id); return true; } static bool HandleDartMetricsList(Isolate* isolate, JSONStream* js) { const Class& metrics_cls = Class::Handle(isolate, GetMetricsClass(isolate)); const String& print_metrics_name = String::Handle(String::New("_printMetrics")); ASSERT(!print_metrics_name.IsNull()); const Function& print_metrics = Function::Handle( isolate, metrics_cls.LookupStaticFunctionAllowPrivate(print_metrics_name)); ASSERT(!print_metrics.IsNull()); const Array& args = Object::empty_array(); const Object& result = Object::Handle(isolate, 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(Isolate* isolate, JSONStream* js, const char* id) { const Class& metrics_cls = Class::Handle(isolate, GetMetricsClass(isolate)); const String& print_metric_name = String::Handle(String::New("_printMetric")); ASSERT(!print_metric_name.IsNull()); const Function& print_metric = Function::Handle( isolate, 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(isolate, DartEntry::InvokeFunction(print_metric, args)); if (!result.IsNull()) { ASSERT(result.IsString()); TextBuffer* buffer = js->buffer(); buffer->AddString(String::Cast(result).ToCString()); return true; } PrintError(js, "Dart Metric %s not found\n", id); return true; } static bool HandleIsolateGetMetricList(Isolate* isolate, 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(isolate, js); } return HandleDartMetricsList(isolate, js); } static bool HandleIsolateGetMetric(Isolate* isolate, 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* kMetricIdPrefix = "metrics/"; static intptr_t kMetricIdPrefixLen = strlen(kMetricIdPrefix); if (strncmp(metric_id, kMetricIdPrefix, kMetricIdPrefixLen) != 0) { PrintError(js, "Metric %s not found\n", metric_id); } // Check if id begins with "metrics/native/". static const char* 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(isolate, js, id); } const char* id = metric_id + kMetricIdPrefixLen; return HandleDartMetric(isolate, js, id); } static bool HandleVMGetMetricList(JSONStream* js) { return false; } static bool HandleVMGetMetric(JSONStream* js) { const char* metric_id = js->LookupParam("metricId"); if (metric_id == NULL) { PrintMissingParamError(js, "metricId"); } return false; } static bool HandleIsolateResume(Isolate* isolate, JSONStream* js) { const char* step_param = js->LookupParam("step"); if (isolate->message_handler()->paused_on_start()) { isolate->message_handler()->set_pause_on_start(false); JSONObject jsobj(js); jsobj.AddProperty("type", "Success"); jsobj.AddProperty("id", ""); { DebuggerEvent resumeEvent(isolate, DebuggerEvent::kIsolateResumed); Service::HandleDebuggerEvent(&resumeEvent); } return true; } if (isolate->message_handler()->paused_on_exit()) { isolate->message_handler()->set_pause_on_exit(false); JSONObject jsobj(js); jsobj.AddProperty("type", "Success"); jsobj.AddProperty("id", ""); // We don't send a resume event because we will be exiting. return true; } if (isolate->debugger()->PauseEvent() != NULL) { if (step_param != NULL) { if (strcmp(step_param, "into") == 0) { isolate->debugger()->SetSingleStep(); } else if (strcmp(step_param, "over") == 0) { isolate->debugger()->SetStepOver(); } else if (strcmp(step_param, "out") == 0) { isolate->debugger()->SetStepOut(); } else { PrintInvalidParamError(js, "step"); return true; } } isolate->Resume(); JSONObject jsobj(js); jsobj.AddProperty("type", "Success"); jsobj.AddProperty("id", ""); return true; } PrintError(js, "VM was not paused"); return true; } static bool HandleIsolateGetBreakpoints(Isolate* isolate, JSONStream* js) { JSONObject jsobj(js); jsobj.AddProperty("type", "BreakpointList"); JSONArray jsarr(&jsobj, "breakpoints"); isolate->debugger()->PrintBreakpointsToJSONArray(&jsarr); return true; } static bool HandleIsolatePause(Isolate* isolate, JSONStream* js) { // TODO(turnidge): Don't double-interrupt the isolate here. isolate->ScheduleInterrupts(Isolate::kApiInterrupt); JSONObject jsobj(js); jsobj.AddProperty("type", "Success"); jsobj.AddProperty("id", ""); return true; } static bool HandleIsolateGetTagProfile(Isolate* isolate, JSONStream* js) { JSONObject miniProfile(js); miniProfile.AddProperty("type", "TagProfile"); miniProfile.AddProperty("id", "profile/tag"); isolate->vm_tag_counters()->PrintToJSONObject(&miniProfile); return true; } static bool HandleIsolateGetCpuProfile(Isolate* isolate, JSONStream* js) { ProfilerService::TagOrder tag_order = ProfilerService::kUserVM; if (js->HasParam("tags")) { if (js->ParamIs("tags", "None")) { tag_order = ProfilerService::kNoTags; } else if (js->ParamIs("tags", "UserVM")) { tag_order = ProfilerService::kUserVM; } else if (js->ParamIs("tags", "UserOnly")) { tag_order = ProfilerService::kUser; } else if (js->ParamIs("tags", "VMUser")) { tag_order = ProfilerService::kVMUser; } else if (js->ParamIs("tags", "VMOnly")) { tag_order = ProfilerService::kVM; } else { PrintInvalidParamError(js, "tags"); return true; } } ProfilerService::PrintJSON(js, tag_order); return true; } static bool HandleIsolateGetAllocationProfile(Isolate* isolate, JSONStream* js) { bool should_reset_accumulator = false; bool should_collect = false; if (js->HasParam("reset")) { if (js->ParamIs("reset", "true")) { should_reset_accumulator = true; } else { PrintInvalidParamError(js, "reset"); return true; } } if (js->HasParam("gc")) { if (js->ParamIs("gc", "full")) { should_collect = true; } else { PrintInvalidParamError(js, "gc"); return true; } } if (should_reset_accumulator) { isolate->UpdateLastAllocationProfileAccumulatorResetTimestamp(); isolate->class_table()->ResetAllocationAccumulators(); } if (should_collect) { isolate->UpdateLastAllocationProfileGCTimestamp(); isolate->heap()->CollectAllGarbage(); } isolate->class_table()->AllocationProfilePrintJSON(js); return true; } static bool HandleIsolateGetHeapMap(Isolate* isolate, JSONStream* js) { isolate->heap()->PrintHeapMapToJSONStream(isolate, js); return true; } static bool HandleIsolateRequestHeapSnapshot(Isolate* isolate, JSONStream* js) { Service::SendGraphEvent(isolate); // TODO(koda): Provide some id that ties this request to async response(s). JSONObject jsobj(js); jsobj.AddProperty("type", "OK"); jsobj.AddProperty("id", "ok"); return true; } void Service::SendGraphEvent(Isolate* isolate) { uint8_t* buffer = NULL; WriteStream stream(&buffer, &allocator, 1 * MB); ObjectGraph graph(isolate); graph.Serialize(&stream); JSONStream js; { JSONObject jsobj(&js); jsobj.AddProperty("type", "ServiceEvent"); jsobj.AddPropertyF("id", "_graphEvent"); jsobj.AddProperty("eventType", "_Graph"); jsobj.AddProperty("isolate", isolate); } const String& message = String::Handle(String::New(js.ToCString())); SendEvent(kEventFamilyDebug, message, buffer, stream.bytes_written()); } class ContainsAddressVisitor : public FindObjectVisitor { public: ContainsAddressVisitor(Isolate* isolate, uword addr) : FindObjectVisitor(isolate), addr_(addr) { } virtual ~ContainsAddressVisitor() { } virtual uword filter_addr() const { return addr_; } virtual bool FindObject(RawObject* obj) const { // Free list elements are not real objects, so skip them. if (obj->IsFreeListElement()) { return false; } uword obj_begin = RawObject::ToAddr(obj); uword obj_end = obj_begin + obj->Size(); return obj_begin <= addr_ && addr_ < obj_end; } private: uword addr_; }; static bool HandleIsolateGetObjectByAddress(Isolate* isolate, 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"); Object& object = Object::Handle(isolate); { NoGCScope no_gc; ContainsAddressVisitor visitor(isolate, addr); object = isolate->heap()->FindObject(&visitor); } object.PrintJSON(js, ref); return true; } static bool HandleIsolateRespondWithMalformedJson(Isolate* isolate, 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 HandleIsolateRespondWithMalformedObject(Isolate* isolate, JSONStream* js) { JSONObject jsobj(js); jsobj.AddProperty("bart", "simpson"); return true; } static bool HandleIsolateGetObject(Isolate* isolate, JSONStream* js) { const char* id = js->LookupParam("objectId"); if (id == NULL) { PrintMissingParamError(js, "objectId"); return true; } // Handle heap objects. ObjectIdRing::LookupResult lookup_result; const Object& obj = Object::Handle(LookupHeapObject(isolate, id, &lookup_result)); if (obj.raw() != Object::sentinel().raw()) { // We found a heap object for this id. Return it. obj.PrintJSON(js, false); return true; } else if (lookup_result == ObjectIdRing::kCollected) { PrintSentinel(js, "objects/collected", ""); return true; } else if (lookup_result == ObjectIdRing::kExpired) { PrintSentinel(js, "objects/expired", ""); return true; } // Handle non-heap objects. SourceBreakpoint* bpt = LookupBreakpoint(isolate, id); if (bpt != NULL) { bpt->PrintJSON(js); return true; } PrintError(js, "Unrecognized object id: %s\n", id); return true; } static bool HandleIsolateGetClassList(Isolate* isolate, JSONStream* js) { ClassTable* table = isolate->class_table(); JSONObject jsobj(js); table->PrintToJSONObject(&jsobj); return true; } static bool HandleIsolateGetTypeArgumentsList(Isolate* isolate, JSONStream* js) { bool only_with_instantiations = false; if (js->ParamIs("onlyWithInstantiations", "true")) { only_with_instantiations = true; } ObjectStore* object_store = isolate->object_store(); const Array& table = Array::Handle(object_store->canonical_type_arguments()); ASSERT(table.Length() > 0); TypeArguments& type_args = TypeArguments::Handle(); const intptr_t table_size = table.Length() - 1; const intptr_t table_used = Smi::Value(Smi::RawCast(table.At(table_size))); 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_size; i++) { type_args ^= table.At(i); if (!type_args.IsNull()) { if (!only_with_instantiations || type_args.HasInstantiations()) { members.AddValue(type_args); } } } return true; } static IsolateMessageHandlerEntry isolate_handlers_new[] = { { "getIsolate", HandleIsolate }, { "getObject", HandleIsolateGetObject }, { "getObjectByAddress", HandleIsolateGetObjectByAddress }, { "getBreakpoints", HandleIsolateGetBreakpoints }, { "pause", HandleIsolatePause }, { "resume", HandleIsolateResume }, { "getStack", HandleIsolateGetStack }, { "getCpuProfile", HandleIsolateGetCpuProfile }, { "getTagProfile", HandleIsolateGetTagProfile }, { "getAllocationProfile", HandleIsolateGetAllocationProfile }, { "getHeapMap", HandleIsolateGetHeapMap }, { "addBreakpoint", HandleIsolateAddBreakpoint }, { "removeBreakpoint", HandleIsolateRemoveBreakpoint }, { "getCoverage", HandleIsolateGetCoverage }, { "eval", HandleIsolateEval }, { "getRetainedSize", HandleIsolateGetRetainedSize }, { "getRetainingPath", HandleIsolateGetRetainingPath }, { "getInboundReferences", HandleIsolateGetInboundReferences }, { "getInstances", HandleIsolateGetInstances }, { "requestHeapSnapshot", HandleIsolateRequestHeapSnapshot }, { "getClassList", HandleIsolateGetClassList }, { "getTypeArgumentsList", HandleIsolateGetTypeArgumentsList }, { "getIsolateMetricList", HandleIsolateGetMetricList }, { "getIsolateMetric", HandleIsolateGetMetric }, { "_echo", HandleIsolateEcho }, { "_triggerEchoEvent", HandleIsolateTriggerEchoEvent }, { "_respondWithMalformedJson", HandleIsolateRespondWithMalformedJson }, { "_respondWithMalformedObject", HandleIsolateRespondWithMalformedObject }, }; static IsolateMessageHandler FindIsolateMessageHandler(const char* method) { intptr_t num_message_handlers = sizeof(isolate_handlers_new) / sizeof(isolate_handlers_new[0]); for (intptr_t i = 0; i < num_message_handlers; i++) { const IsolateMessageHandlerEntry& entry = isolate_handlers_new[i]; if (strcmp(method, entry.method) == 0) { return entry.handler; } } if (FLAG_trace_service) { OS::Print("Service has no isolate message handler for <%s>\n", method); } return NULL; } void Service::HandleRootMessage(const Instance& msg_instance) { Isolate* isolate = Isolate::Current(); ASSERT(!msg_instance.IsNull()); ASSERT(msg_instance.IsArray()); { StackZone zone(isolate); HANDLESCOPE(isolate); const Array& msg = Array::Cast(msg_instance); ASSERT(msg.Length() == 5); Instance& reply_port = Instance::Handle(isolate); String& method = String::Handle(isolate); Array& param_keys = Array::Handle(isolate); Array& param_values = Array::Handle(isolate); reply_port ^= msg.At(1); method ^= msg.At(2); param_keys ^= msg.At(3); param_values ^= msg.At(4); ASSERT(!method.IsNull()); ASSERT(!param_keys.IsNull()); ASSERT(!param_values.IsNull()); ASSERT(param_keys.Length() == param_values.Length()); if (!reply_port.IsSendPort()) { FATAL("SendPort expected."); } RootMessageHandler handler = FindRootMessageHandler(method.ToCString()); { JSONStream js; js.Setup(zone.GetZone(), SendPort::Cast(reply_port).Id(), method, param_keys, param_values); if (handler == NULL) { // Check for an embedder handler. EmbedderServiceHandler* e_handler = FindRootEmbedderHandler(method.ToCString()); if (e_handler != NULL) { EmbedderHandleMessage(e_handler, &js); } else { if (FindIsolateMessageHandler(method.ToCString()) != NULL) { PrintMissingParamError(&js, "isolateId"); } else { PrintError(&js, "Unrecognized method: %s", method.ToCString()); } } js.PostReply(); } else { if (handler(&js)) { // Handler returns true if the reply is ready to be posted. // TODO(johnmccutchan): Support asynchronous replies. js.PostReply(); } } } } } static bool HandleRootEcho(JSONStream* js) { JSONObject jsobj(js); return HandleCommonEcho(&jsobj, js); } class ServiceIsolateVisitor : public IsolateVisitor { public: explicit ServiceIsolateVisitor(JSONArray* jsarr) : jsarr_(jsarr) { } virtual ~ServiceIsolateVisitor() {} void VisitIsolate(Isolate* isolate) { if (isolate != Dart::vm_isolate() && !Service::IsServiceIsolate(isolate)) { jsarr_->AddValue(isolate); } } private: JSONArray* jsarr_; }; static bool HandleVM(JSONStream* js) { Isolate* isolate = Isolate::Current(); JSONObject jsobj(js); jsobj.AddProperty("type", "VM"); jsobj.AddProperty("id", "vm"); jsobj.AddProperty("architectureBits", static_cast(kBitsPerWord)); jsobj.AddProperty("targetCPU", CPU::Id()); jsobj.AddProperty("hostCPU", HostCPUFeatures::hardware()); jsobj.AddPropertyF("date", "%" Pd64 "", OS::GetCurrentTimeMillis()); jsobj.AddProperty("version", Version::String()); // Send pid as a string because it allows us to avoid any issues with // pids > 53-bits (when consumed by JavaScript). // TODO(johnmccutchan): Codify how integers are sent across the service. jsobj.AddPropertyF("pid", "%" Pd "", OS::ProcessId()); jsobj.AddProperty("assertsEnabled", isolate->AssertsEnabled()); jsobj.AddProperty("typeChecksEnabled", isolate->TypeChecksEnabled()); int64_t start_time_micros = Dart::vm_isolate()->start_time(); int64_t uptime_micros = (OS::GetCurrentTimeMicros() - start_time_micros); double seconds = (static_cast(uptime_micros) / static_cast(kMicrosecondsPerSecond)); jsobj.AddProperty("uptime", seconds); // Construct the isolate list. { JSONArray jsarr(&jsobj, "isolates"); ServiceIsolateVisitor visitor(&jsarr); Isolate::VisitIsolates(&visitor); } return true; } static bool HandleVMFlagList(JSONStream* js) { Flags::PrintJSON(js); return true; } static bool HandleVMSetFlag(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; } JSONObject jsobj(js); const char* error = NULL; if (Flags::SetFlag(flag_name, flag_value, &error)) { jsobj.AddProperty("type", "Success"); jsobj.AddProperty("id", ""); return true; } else { jsobj.AddProperty("type", "Failure"); jsobj.AddProperty("id", ""); jsobj.AddProperty("message", error); return true; } } static RootMessageHandlerEntry root_handlers_new[] = { { "getVM", HandleVM }, { "getFlagList", HandleVMFlagList }, { "setFlag", HandleVMSetFlag }, { "getVMMetricList", HandleVMGetMetricList }, { "getVMMetric", HandleVMGetMetric }, { "_echo", HandleRootEcho }, }; static RootMessageHandler FindRootMessageHandler(const char* method) { intptr_t num_message_handlers = sizeof(root_handlers_new) / sizeof(root_handlers_new[0]); for (intptr_t i = 0; i < num_message_handlers; i++) { const RootMessageHandlerEntry& entry = root_handlers_new[i]; if (strcmp(method, entry.method) == 0) { return entry.handler; } } if (FLAG_trace_service) { OS::Print("vm-service: No root message handler for <%s>.\n", method); } return NULL; } void Service::SendEvent(intptr_t eventId, const Object& eventMessage) { if (!IsRunning()) { return; } Isolate* isolate = Isolate::Current(); ASSERT(isolate != NULL); HANDLESCOPE(isolate); // Construct a list of the form [eventId, eventMessage]. const Array& list = Array::Handle(Array::New(2)); ASSERT(!list.IsNull()); list.SetAt(0, Integer::Handle(Integer::New(eventId))); list.SetAt(1, eventMessage); // Push the event to port_. uint8_t* data = NULL; MessageWriter writer(&data, &allocator, false); writer.WriteMessage(list); intptr_t len = writer.BytesWritten(); if (FLAG_trace_service) { OS::Print("vm-service: Pushing event of type %" Pd ", len %" Pd "\n", eventId, len); } // TODO(turnidge): For now we ignore failure to send an event. Revisit? PortMap::PostMessage( new Message(service_port_, data, len, Message::kNormalPriority)); } void Service::SendEvent(intptr_t eventId, const String& meta, const uint8_t* data, intptr_t size) { // Bitstream: [meta data size (big-endian 64 bit)] [meta data (UTF-8)] [data] const intptr_t meta_bytes = Utf8::Length(meta); const intptr_t total_bytes = sizeof(uint64_t) + meta_bytes + size; const TypedData& message = TypedData::Handle( TypedData::New(kTypedDataUint8ArrayCid, total_bytes)); intptr_t offset = 0; // TODO(koda): Rename these methods SetHostUint64, etc. message.SetUint64(0, Utils::HostToBigEndian64(meta_bytes)); offset += sizeof(uint64_t); { NoGCScope no_gc; meta.ToUTF8(static_cast(message.DataAddr(offset)), meta_bytes); offset += meta_bytes; } // TODO(koda): It would be nice to avoid this copy (requires changes to // MessageWriter code). { NoGCScope no_gc; memmove(message.DataAddr(offset), data, size); offset += size; } ASSERT(offset == total_bytes); SendEvent(eventId, message); } void Service::HandleGCEvent(GCEvent* event) { JSONStream js; event->PrintJSON(&js); const String& message = String::Handle(String::New(js.ToCString())); SendEvent(kEventFamilyGC, message); } void Service::HandleDebuggerEvent(DebuggerEvent* event) { JSONStream js; event->PrintJSON(&js); const String& message = String::Handle(String::New(js.ToCString())); SendEvent(kEventFamilyDebug, message); } void Service::EmbedderHandleMessage(EmbedderServiceHandler* handler, JSONStream* js) { ASSERT(handler != NULL); Dart_ServiceRequestCallback callback = handler->callback(); ASSERT(callback != NULL); const char* r = NULL; const char* name = js->method(); const char** keys = js->param_keys(); const char** values = js->param_values(); r = callback(name, keys, values, js->num_params(), handler->user_data()); ASSERT(r != NULL); // TODO(johnmccutchan): Allow for NULL returns? TextBuffer* buffer = js->buffer(); buffer->AddString(r); free(const_cast(r)); } 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; } 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; } } // namespace dart