// 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 "bin/vmservice_impl.h" #include "include/dart_api.h" #include "bin/builtin.h" #include "bin/dartutils.h" #include "bin/isolate_data.h" #include "bin/resources.h" #include "bin/thread.h" #include "vm/dart_api_impl.h" #include "vm/dart_entry.h" #include "vm/isolate.h" #include "vm/message.h" #include "vm/native_entry.h" #include "vm/native_arguments.h" #include "vm/object.h" #include "vm/port.h" #include "vm/snapshot.h" namespace dart { namespace bin { // snapshot_buffer points to a snapshot if we link in a snapshot otherwise // it is initialized to NULL. extern const uint8_t* snapshot_buffer; #define RETURN_ERROR_HANDLE(handle) \ if (Dart_IsError(handle)) { \ return handle; \ } #define SHUTDOWN_ON_ERROR(handle) \ if (Dart_IsError(handle)) { \ error_msg_ = strdup(Dart_GetError(handle)); \ Dart_ExitScope(); \ Dart_ShutdownIsolate(); \ return false; \ } #define kLibraryResourceNamePrefix "/vmservice" static const char* kVMServiceIOLibraryScriptResourceName = kLibraryResourceNamePrefix "/vmservice_io.dart"; static const char* kVMServiceLibraryName = kLibraryResourceNamePrefix "/vmservice.dart"; #define kClientResourceNamePrefix "/vmservice/client/deployed/web" Dart_Isolate VmService::isolate_ = NULL; Dart_Port VmService::port_ = ILLEGAL_PORT; dart::Monitor* VmService::monitor_ = NULL; const char* VmService::error_msg_ = NULL; // These must be kept in sync with vmservice/constants.dart #define VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID 1 #define VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID 2 static Dart_NativeFunction VmServiceNativeResolver(Dart_Handle name, int num_arguments, bool* auto_setup_scope); bool VmService::Start(intptr_t server_port) { monitor_ = new dart::Monitor(); ASSERT(monitor_ != NULL); error_msg_ = NULL; { // Take lock before spawning new thread. MonitorLocker ml(monitor_); // Spawn new thread. dart::Thread::Start(ThreadMain, server_port); // Wait until service is running on spawned thread. ml.Wait(); } return port_ != ILLEGAL_PORT; } bool VmService::_Start(intptr_t server_port) { ASSERT(isolate_ == NULL); char* error = NULL; const char* script_uri = "vmservice:"; IsolateData* isolate_data = new IsolateData(script_uri); isolate_ = Dart_CreateIsolate(script_uri, "main", snapshot_buffer, isolate_data, &error); if (isolate_ == NULL) { error_msg_ = error; return false; } Dart_EnterScope(); if (snapshot_buffer != NULL) { // Setup the native resolver as the snapshot does not carry it. Builtin::SetNativeResolver(Builtin::kBuiltinLibrary); Builtin::SetNativeResolver(Builtin::kIOLibrary); } // Set up the library tag handler for this isolate. Dart_Handle result = Dart_SetLibraryTagHandler(LibraryTagHandler); SHUTDOWN_ON_ERROR(result); // Load the specified application script into the newly created isolate. // Prepare builtin and its dependent libraries for use to resolve URIs. // The builtin library is part of the core snapshot and would already be // available here in the case of script snapshot loading. Dart_Handle builtin_lib = Builtin::LoadAndCheckLibrary(Builtin::kBuiltinLibrary); SHUTDOWN_ON_ERROR(builtin_lib); // Prepare for script loading by setting up the 'print' and 'timer' // closures and setting up 'package root' for URI resolution. result = DartUtils::PrepareForScriptLoading("", builtin_lib); SHUTDOWN_ON_ERROR(result); { // Load source into service isolate. Dart_Handle library = LoadScript(kVMServiceIOLibraryScriptResourceName); SHUTDOWN_ON_ERROR(library); } // Make the isolate runnable so that it is ready to handle messages. Dart_ExitScope(); Dart_ExitIsolate(); bool retval = Dart_IsolateMakeRunnable(isolate_); if (!retval) { Dart_EnterIsolate(isolate_); Dart_ShutdownIsolate(); error_msg_ = "Invalid isolate state - Unable to make it runnable."; return false; } Dart_EnterIsolate(isolate_); Dart_EnterScope(); Dart_Handle library = Dart_RootLibrary(); // Set requested TCP port. DartUtils::SetIntegerField(library, "_port", server_port); result = Dart_Invoke(library, DartUtils::NewString("main"), 0, NULL); SHUTDOWN_ON_ERROR(result); // Retrieve the ReceivePort that the service is waiting on. The _receivePort // variable is setup in the call to main. Dart_Handle receivePort = Dart_GetField(library, DartUtils::NewString("_receivePort")); SHUTDOWN_ON_ERROR(receivePort); { // Extract the Dart_Port from the receive port. HANDLESCOPE(Isolate::Current()); const Object& unwrapped_rp = Object::Handle(Api::UnwrapHandle(receivePort)); const Instance& rp = Instance::Cast(unwrapped_rp); // Extract ReceivePort port id. const Object& rp_id_obj = Object::Handle(DartLibraryCalls::PortGetId(rp)); if (rp_id_obj.IsError()) { const Error& error = Error::Cast(rp_id_obj); error_msg_ = strdup(error.ToErrorCString()); Dart_ExitScope(); Dart_ShutdownIsolate(); return false; } ASSERT(rp_id_obj.IsSmi() || rp_id_obj.IsMint()); Integer& id = Integer::Handle(); id ^= rp_id_obj.raw(); port_ = static_cast(id.AsInt64Value()); } Dart_Handle library_name = Dart_NewStringFromCString(kVMServiceLibraryName); library = Dart_LookupLibrary(library_name); SHUTDOWN_ON_ERROR(library); result = LoadResources(library); SHUTDOWN_ON_ERROR(result); Dart_ExitScope(); Dart_ExitIsolate(); return true; } void VmService::_Stop() { port_ = ILLEGAL_PORT; } const char* VmService::GetErrorMessage() { return error_msg_ == NULL ? "No error." : error_msg_; } Dart_Port VmService::port() { return port_; } bool VmService::IsRunning() { return port_ != ILLEGAL_PORT; } Dart_Handle VmService::GetSource(const char* name) { const char* vmservice_source = NULL; int r = Resources::ResourceLookup(name, &vmservice_source); ASSERT(r != Resources::kNoSuchInstance); return Dart_NewStringFromCString(vmservice_source); } Dart_Handle VmService::LoadScript(const char* name) { Dart_Handle url = Dart_NewStringFromCString(name); Dart_Handle source = GetSource(name); return Dart_LoadScript(url, source, 0, 0); } Dart_Handle VmService::LoadSource(Dart_Handle library, const char* name) { Dart_Handle url = Dart_NewStringFromCString(name); Dart_Handle source = GetSource(name); return Dart_LoadSource(library, url, source); } Dart_Handle VmService::LoadSources(Dart_Handle library, const char* names[]) { Dart_Handle result = Dart_Null(); for (int i = 0; names[i] != NULL; i++) { result = LoadSource(library, names[i]); if (Dart_IsError(result)) { break; } } return result; } Dart_Handle VmService::LoadResource(Dart_Handle library, const char* resource_name, const char* prefix) { intptr_t prefix_len = strlen(prefix); // Prepare for invoke call. Dart_Handle name = Dart_NewStringFromCString(resource_name+prefix_len); RETURN_ERROR_HANDLE(name); const char* data_buffer = NULL; int data_buffer_length = Resources::ResourceLookup(resource_name, &data_buffer); if (data_buffer_length == Resources::kNoSuchInstance) { printf("Could not find %s %s\n", resource_name, resource_name+prefix_len); } ASSERT(data_buffer_length != Resources::kNoSuchInstance); Dart_Handle data_list = Dart_NewTypedData(Dart_TypedData_kUint8, data_buffer_length); RETURN_ERROR_HANDLE(data_list); Dart_TypedData_Type type = Dart_TypedData_kInvalid; void* data_list_buffer = NULL; intptr_t data_list_buffer_length = 0; Dart_Handle result = Dart_TypedDataAcquireData(data_list, &type, &data_list_buffer, &data_list_buffer_length); RETURN_ERROR_HANDLE(result); ASSERT(data_buffer_length == data_list_buffer_length); ASSERT(data_list_buffer != NULL); ASSERT(type = Dart_TypedData_kUint8); memmove(data_list_buffer, &data_buffer[0], data_buffer_length); result = Dart_TypedDataReleaseData(data_list); RETURN_ERROR_HANDLE(result); // Make invoke call. const intptr_t kNumArgs = 2; Dart_Handle args[kNumArgs] = { name, data_list }; result = Dart_Invoke(library, Dart_NewStringFromCString("_addResource"), kNumArgs, args); return result; } Dart_Handle VmService::LoadResources(Dart_Handle library) { Dart_Handle result = Dart_Null(); intptr_t prefixLen = strlen(kClientResourceNamePrefix); for (intptr_t i = 0; i < Resources::get_resource_count(); i++) { const char* path = Resources::get_resource_path(i); if (!strncmp(path, kClientResourceNamePrefix, prefixLen)) { result = LoadResource(library, path, kClientResourceNamePrefix); if (Dart_IsError(result)) { break; } } } return result; } static bool IsVMServiceURL(const char* url) { static const intptr_t kLibraryResourceNamePrefixLen = strlen(kLibraryResourceNamePrefix); return 0 == strncmp(kLibraryResourceNamePrefix, url, kLibraryResourceNamePrefixLen); } static bool IsVMServiceLibrary(const char* url) { return 0 == strcmp(kVMServiceLibraryName, url); } Dart_Handle VmService::LibraryTagHandler(Dart_LibraryTag tag, Dart_Handle library, Dart_Handle url) { if (!Dart_IsLibrary(library)) { return Dart_NewApiError("not a library"); } if (!Dart_IsString(url)) { return Dart_NewApiError("url is not a string"); } const char* url_string = NULL; Dart_Handle result = Dart_StringToCString(url, &url_string); if (Dart_IsError(result)) { return result; } Dart_Handle library_url = Dart_LibraryUrl(library); const char* library_url_string = NULL; result = Dart_StringToCString(library_url, &library_url_string); if (Dart_IsError(result)) { return result; } bool is_vm_service_url = IsVMServiceURL(url_string); if (!is_vm_service_url) { // Pass to DartUtils. return DartUtils::LibraryTagHandler(tag, library, url); } switch (tag) { case Dart_kCanonicalizeUrl: // The URL is already canonicalized. return url; break; case Dart_kImportTag: { Dart_Handle source = GetSource(url_string); if (Dart_IsError(source)) { return source; } Dart_Handle lib = Dart_LoadLibrary(url, source); if (Dart_IsError(lib)) { return lib; } if (IsVMServiceLibrary(url_string)) { // Install native resolver for this library. result = Dart_SetNativeResolver(lib, VmServiceNativeResolver); if (Dart_IsError(result)) { return result; } } return lib; } break; case Dart_kSourceTag: { Dart_Handle source = GetSource(url_string); if (Dart_IsError(source)) { return source; } return Dart_LoadSource(library, url, source); } break; default: UNIMPLEMENTED(); break; } UNREACHABLE(); return result; } void VmService::ThreadMain(uword parameters) { ASSERT(Dart_CurrentIsolate() == NULL); ASSERT(isolate_ == NULL); intptr_t server_port = static_cast(parameters); ASSERT(server_port >= 0); // Lock scope. { MonitorLocker ml(monitor_); bool r = _Start(server_port); if (!r) { port_ = ILLEGAL_PORT; monitor_->Notify(); return; } Dart_EnterIsolate(isolate_); Dart_EnterScope(); Dart_Handle receievePort = Dart_GetReceivePort(port_); ASSERT(!Dart_IsError(receievePort)); monitor_->Notify(); } // Keep handling messages until the last active receive port is closed. Dart_Handle result = Dart_RunLoop(); if (Dart_IsError(result)) { printf("VmService has exited with an error:\n%s\n", Dart_GetError(result)); } _Stop(); Dart_ExitScope(); Dart_ExitIsolate(); } static Dart_Handle MakeServiceControlMessage(Dart_Port port_id, intptr_t code, Dart_Handle name) { Dart_Handle result; Dart_Handle list = Dart_NewList(4); ASSERT(!Dart_IsError(list)); Dart_Handle code_handle = Dart_NewInteger(code); ASSERT(!Dart_IsError(code_handle)); result = Dart_ListSetAt(list, 0, code_handle); ASSERT(!Dart_IsError(result)); Dart_Handle port_id_handle = Dart_NewInteger(port_id); ASSERT(!Dart_IsError(port_id_handle)); result = Dart_ListSetAt(list, 1, port_id_handle); ASSERT(!Dart_IsError(result)); Dart_Handle sendPort = Dart_NewSendPort(port_id); ASSERT(!Dart_IsError(sendPort)); result = Dart_ListSetAt(list, 2, sendPort); ASSERT(!Dart_IsError(result)); result = Dart_ListSetAt(list, 3, name); ASSERT(!Dart_IsError(result)); return list; } bool VmService::SendIsolateStartupMessage() { if (!IsRunning()) { return false; } Isolate* isolate = Isolate::Current(); ASSERT(isolate != NULL); HANDLESCOPE(isolate); Dart_Handle name = Api::NewHandle(isolate, String::New(isolate->name())); ASSERT(!Dart_IsError(name)); Dart_Handle list = MakeServiceControlMessage(Dart_GetMainPortId(), VM_SERVICE_ISOLATE_STARTUP_MESSAGE_ID, name); ASSERT(!Dart_IsError(list)); return Dart_Post(port_, list); } bool VmService::SendIsolateShutdownMessage() { if (!IsRunning()) { return false; } Isolate* isolate = Isolate::Current(); ASSERT(isolate != NULL); HANDLESCOPE(isolate); Dart_Handle list = MakeServiceControlMessage(Dart_GetMainPortId(), VM_SERVICE_ISOLATE_SHUTDOWN_MESSAGE_ID, Dart_Null()); ASSERT(!Dart_IsError(list)); return Dart_Post(port_, list); } void VmService::VmServiceShutdownCallback(void* callback_data) { ASSERT(Dart_CurrentIsolate() != NULL); Dart_EnterScope(); VmService::SendIsolateShutdownMessage(); Dart_ExitScope(); } 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 SendServiceMessage(Dart_NativeArguments args) { NativeArguments* arguments = reinterpret_cast(args); Isolate* isolate = arguments->isolate(); StackZone zone(isolate); HANDLESCOPE(isolate); GET_NON_NULL_NATIVE_ARGUMENT(Instance, sp, arguments->NativeArgAt(0)); GET_NON_NULL_NATIVE_ARGUMENT(Instance, message, arguments->NativeArgAt(1)); // Extract SendPort port id. const Object& sp_id_obj = Object::Handle(DartLibraryCalls::PortGetId(sp)); if (sp_id_obj.IsError()) { Exceptions::PropagateError(Error::Cast(sp_id_obj)); } Integer& id = Integer::Handle(); id ^= sp_id_obj.raw(); Dart_Port sp_id = static_cast(id.AsInt64Value()); ASSERT(sp_id != ILLEGAL_PORT); // Serialize message. uint8_t* data = NULL; MessageWriter writer(&data, &allocator); 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)); } struct VmServiceNativeEntry { const char* name; int num_arguments; Dart_NativeFunction function; }; static VmServiceNativeEntry _VmServiceNativeEntries[] = { {"VMService_SendServiceMessage", 2, SendServiceMessage} }; 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) && (num_arguments == entry.num_arguments)) { return entry.function; } } return NULL; } } // namespace bin } // namespace dart