| // Copyright (c) 2015, 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_isolate.h" |
| |
| #include "vm/compiler.h" |
| #include "vm/dart_api_impl.h" |
| #include "vm/dart_entry.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_store.h" |
| #include "vm/port.h" |
| #include "vm/service.h" |
| #include "vm/symbols.h" |
| #include "vm/thread_pool.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."); |
| |
| 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<const uint8_t*>(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); |
| }; |
| |
| |
| static uint8_t* allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) { |
| void* new_ptr = realloc(reinterpret_cast<void*>(ptr), new_size); |
| return reinterpret_cast<uint8_t*>(new_ptr); |
| } |
| |
| |
| 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(); |
| } |
| |
| |
| // 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(); |
| } |
| |
| |
| const char* ServiceIsolate::kName = "vm-service"; |
| Isolate* ServiceIsolate::isolate_ = NULL; |
| Dart_Port ServiceIsolate::port_ = ILLEGAL_PORT; |
| Dart_Port ServiceIsolate::load_port_ = ILLEGAL_PORT; |
| Dart_IsolateCreateCallback ServiceIsolate::create_callback_ = NULL; |
| Monitor* ServiceIsolate::monitor_ = NULL; |
| bool ServiceIsolate::initializing_ = true; |
| |
| |
| class RegisterRunningIsolatesVisitor : public IsolateVisitor { |
| public: |
| explicit RegisterRunningIsolatesVisitor(Isolate* service_isolate) |
| : IsolateVisitor(), |
| register_function_(Function::Handle(service_isolate)), |
| service_isolate_(service_isolate) { |
| ASSERT(ServiceIsolate::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(ServiceIsolate::IsServiceIsolate(Isolate::Current())); |
| if (ServiceIsolate::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_; |
| }; |
| |
| |
| |
| class ServiceIsolateNatives : public AllStatic { |
| public: |
| static void SendIsolateServiceMessage(Dart_NativeArguments args) { |
| NativeArguments* arguments = reinterpret_cast<NativeArguments*>(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? |
| bool result = PortMap::PostMessage( |
| new Message(sp.Id(), data, writer.BytesWritten(), |
| Message::kOOBPriority)); |
| arguments->SetReturn(Bool::Get(result)); |
| } |
| |
| static void SendRootServiceMessage(Dart_NativeArguments args) { |
| NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args); |
| Isolate* isolate = arguments->isolate(); |
| StackZone zone(isolate); |
| HANDLESCOPE(isolate); |
| GET_NON_NULL_NATIVE_ARGUMENT(Array, message, arguments->NativeArgAt(0)); |
| Service::HandleRootMessage(message); |
| } |
| |
| static void SetEventMask(Dart_NativeArguments args) { |
| NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args); |
| Isolate* isolate = arguments->isolate(); |
| StackZone zone(isolate); |
| HANDLESCOPE(isolate); |
| GET_NON_NULL_NATIVE_ARGUMENT(Integer, mask, arguments->NativeArgAt(0)); |
| Service::SetEventMask(mask.AsTruncatedUint32Value()); |
| } |
| |
| static void OnStart(Dart_NativeArguments args) { |
| NativeArguments* arguments = reinterpret_cast<NativeArguments*>(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); |
| ServiceIsolate::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 ServiceNativeEntry { |
| const char* name; |
| int num_arguments; |
| Dart_NativeFunction function; |
| }; |
| |
| |
| static ServiceNativeEntry _ServiceNativeEntries[] = { |
| {"VMService_SendIsolateServiceMessage", 2, |
| ServiceIsolateNatives::SendIsolateServiceMessage}, |
| {"VMService_SendRootServiceMessage", 1, |
| ServiceIsolateNatives::SendRootServiceMessage}, |
| {"VMService_SetEventMask", 1, |
| ServiceIsolateNatives::SetEventMask}, |
| {"VMService_OnStart", 0, |
| ServiceIsolateNatives::OnStart }, |
| }; |
| |
| |
| static Dart_NativeFunction ServiceNativeResolver(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(_ServiceNativeEntries) / |
| sizeof(_ServiceNativeEntries[0]); |
| for (intptr_t i = 0; i < n; i++) { |
| ServiceNativeEntry entry = _ServiceNativeEntries[i]; |
| if ((strcmp(function_name, entry.name) == 0) && |
| (num_arguments == entry.num_arguments)) { |
| return entry.function; |
| } |
| } |
| return NULL; |
| } |
| |
| |
| bool ServiceIsolate::NameEquals(const char* name) { |
| ASSERT(name != NULL); |
| return strcmp(name, kName) == 0; |
| } |
| |
| |
| bool ServiceIsolate::Exists() { |
| MonitorLocker ml(monitor_); |
| return isolate_ != NULL; |
| } |
| |
| |
| bool ServiceIsolate::IsRunning() { |
| MonitorLocker ml(monitor_); |
| return (port_ != ILLEGAL_PORT) && (isolate_ != NULL); |
| } |
| |
| |
| bool ServiceIsolate::IsServiceIsolate(Isolate* isolate) { |
| MonitorLocker ml(monitor_); |
| return isolate == isolate_; |
| } |
| |
| |
| Dart_Port ServiceIsolate::Port() { |
| MonitorLocker ml(monitor_); |
| return port_; |
| } |
| |
| |
| Dart_Port ServiceIsolate::WaitForLoadPort() { |
| MonitorLocker ml(monitor_); |
| |
| while (initializing_ && (load_port_ == ILLEGAL_PORT)) { |
| ml.Wait(); |
| } |
| |
| return load_port_; |
| } |
| |
| |
| Dart_Port ServiceIsolate::LoadPort() { |
| MonitorLocker ml(monitor_); |
| return load_port_; |
| } |
| |
| |
| bool ServiceIsolate::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(port_, data, len, Message::kNormalPriority)); |
| } |
| |
| |
| bool ServiceIsolate::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(port_, data, len, Message::kNormalPriority)); |
| } |
| |
| |
| void ServiceIsolate::SetServicePort(Dart_Port port) { |
| MonitorLocker ml(monitor_); |
| port_ = port; |
| } |
| |
| |
| void ServiceIsolate::SetServiceIsolate(Isolate* isolate) { |
| MonitorLocker ml(monitor_); |
| isolate_ = isolate; |
| if (isolate_ != NULL) { |
| isolate_->is_service_isolate_ = true; |
| } |
| } |
| |
| void ServiceIsolate::SetLoadPort(Dart_Port port) { |
| MonitorLocker ml(monitor_); |
| load_port_ = port; |
| } |
| |
| |
| void ServiceIsolate::MaybeInjectVMServiceLibrary(Isolate* isolate) { |
| ASSERT(isolate != NULL); |
| ASSERT(isolate->name() != NULL); |
| if (!ServiceIsolate::NameEquals(isolate->name())) { |
| // Not service isolate. |
| return; |
| } |
| if (Exists()) { |
| // 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(ServiceNativeResolver); |
| |
| // 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<const uint8_t*>(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 ServiceIsolate::FinishedInitializing() { |
| MonitorLocker ml(monitor_); |
| initializing_ = false; |
| ml.NotifyAll(); |
| } |
| |
| |
| class RunServiceTask : public ThreadPool::Task { |
| public: |
| virtual void Run() { |
| ASSERT(Isolate::Current() == NULL); |
| char* error = NULL; |
| Isolate* isolate = NULL; |
| |
| Dart_IsolateCreateCallback create_callback = |
| ServiceIsolate::create_callback(); |
| // TODO(johnmccutchan): Support starting up service isolate without embedder |
| // provided isolate creation callback. |
| if (create_callback == NULL) { |
| ServiceIsolate::FinishedInitializing(); |
| return; |
| } |
| |
| isolate = |
| reinterpret_cast<Isolate*>(create_callback(ServiceIsolate::kName, |
| NULL, |
| NULL, |
| NULL, |
| &error)); |
| if (isolate == NULL) { |
| OS::PrintErr("vm-service: Isolate creation error: %s\n", error); |
| ServiceIsolate::FinishedInitializing(); |
| return; |
| } |
| |
| Isolate::SetCurrent(NULL); |
| |
| RunMain(isolate); |
| |
| ServiceIsolate::FinishedInitializing(); |
| |
| isolate->message_handler()->Run(Dart::thread_pool(), |
| NULL, |
| ShutdownIsolate, |
| reinterpret_cast<uword>(isolate)); |
| } |
| |
| protected: |
| static void ShutdownIsolate(uword parameter) { |
| Isolate* isolate = reinterpret_cast<Isolate*>(parameter); |
| ASSERT(ServiceIsolate::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(); |
| } |
| ServiceIsolate::SetServiceIsolate(NULL); |
| ServiceIsolate::SetServicePort(ILLEGAL_PORT); |
| if (FLAG_trace_service) { |
| OS::Print("vm-service: Shutdown.\n"); |
| } |
| } |
| |
| 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); |
| ServiceIsolate::SetLoadPort(rp.Id()); |
| } |
| }; |
| |
| |
| void ServiceIsolate::Run() { |
| 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()); |
| } |
| |
| |
| Dart_Handle ServiceIsolate::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++; |
| } |
| FATAL1("vm-service: Could not find embedded source file: %s ", name); |
| return Dart_Null(); |
| } |
| |
| |
| Dart_Handle ServiceIsolate::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("ServiceIsolate::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); |
| } |
| |
| } // namespace dart |