blob: 04800f6672de98e3a4ecdd021fef4de63fb4dc84 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/sys_info.h"
#include "crypto/random.h"
#include "dart/runtime/include/dart_api.h"
#include "dart/runtime/include/dart_native_api.h"
#include "mojo/dart/embedder/builtin.h"
#include "mojo/dart/embedder/dart_controller.h"
#include "mojo/dart/embedder/isolate_data.h"
#include "mojo/public/c/system/core.h"
namespace mojo {
namespace dart {
extern const uint8_t* vm_isolate_snapshot_buffer;
extern const uint8_t* isolate_snapshot_buffer;
const char* kDartScheme = "dart:";
const char* kAsyncLibURL = "dart:async";
const char* kInternalLibURL = "dart:_internal";
const char* kIsolateLibURL = "dart:isolate";
const char* kIOLibURL = "dart:io";
const char* kCoreLibURL = "dart:core";
static bool IsDartSchemeURL(const char* url_name) {
static const intptr_t kDartSchemeLen = strlen(kDartScheme);
// If the URL starts with "dart:" then it is considered as a special
// library URL which is handled differently from other URLs.
return (strncmp(url_name, kDartScheme, kDartSchemeLen) == 0);
}
static bool IsServiceIsolateURL(const char* url_name) {
if (url_name == nullptr) {
return false;
}
static const intptr_t kServiceIsolateNameLen =
strlen(DART_VM_SERVICE_ISOLATE_NAME);
return (strncmp(url_name,
DART_VM_SERVICE_ISOLATE_NAME,
kServiceIsolateNameLen) == 0);
}
static bool IsDartIOLibURL(const char* url_name) {
return (strcmp(url_name, kIOLibURL) == 0);
}
static void ReportScriptError(Dart_Handle handle) {
// The normal DART_CHECK_VALID macro displays error information and a stack
// dump for the C++ application, which is confusing. Only show the Dart error.
if (Dart_IsError((handle))) {
LOG(ERROR) << "Dart runtime error:\n" << Dart_GetError(handle) << "\n";
}
}
Dart_Handle ResolveUri(Dart_Handle library_url,
Dart_Handle url,
Dart_Handle builtin_lib) {
const int kNumArgs = 2;
Dart_Handle dart_args[kNumArgs];
dart_args[0] = library_url;
dart_args[1] = url;
return Dart_Invoke(builtin_lib,
Dart_NewStringFromCString("_resolveUri"),
kNumArgs,
dart_args);
}
static Dart_Handle LoadDataAsync_Invoke(Dart_Handle tag,
Dart_Handle url,
Dart_Handle library_url,
Dart_Handle builtin_lib,
Dart_Handle data) {
const int kNumArgs = 4;
Dart_Handle dart_args[kNumArgs];
dart_args[0] = tag;
dart_args[1] = url;
dart_args[2] = library_url;
dart_args[3] = data;
return Dart_Invoke(builtin_lib,
Dart_NewStringFromCString("_loadDataAsync"),
kNumArgs,
dart_args);
}
static Dart_Handle 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");
}
Dart_Handle library_url = Dart_LibraryUrl(library);
const char* library_url_string = nullptr;
Dart_Handle result = Dart_StringToCString(library_url, &library_url_string);
if (Dart_IsError(result)) {
return result;
}
bool is_io_library = IsDartIOLibURL(library_url_string);
// Handle IO library.
if (is_io_library) {
return Builtin::NewError(
"The built-in io library is not available in this embedding: %s.\n",
library_url_string);
}
// Handle URI canonicalization requests.
const char* url_string = nullptr;
result = Dart_StringToCString(url, &url_string);
if (tag == Dart_kCanonicalizeUrl) {
if (Dart_IsError(result)) {
return result;
}
const bool is_internal_scheme_url = IsDartSchemeURL(url_string);
// If this is a Dart Scheme URL, or a Mojo Scheme URL, then it is not
// modified as it will be handled internally.
if (is_internal_scheme_url) {
return url;
}
// Resolve the url within the context of the library's URL.
Dart_Handle builtin_lib =
Builtin::GetLibrary(Builtin::kBuiltinLibrary);
return ResolveUri(library_url, url, builtin_lib);
}
Dart_Handle builtin_lib =
Builtin::GetLibrary(Builtin::kBuiltinLibrary);
// Handle 'import' or 'part' requests for all other URIs. Call dart code to
// read the source code asynchronously.
return LoadDataAsync_Invoke(
Dart_NewInteger(tag), url, library_url, builtin_lib, Dart_Null());
}
static Dart_Handle SetWorkingDirectory(Dart_Handle builtin_lib) {
base::FilePath current_dir;
PathService::Get(base::DIR_CURRENT, &current_dir);
const int kNumArgs = 1;
Dart_Handle dart_args[kNumArgs];
std::string current_dir_string = current_dir.AsUTF8Unsafe();
dart_args[0] = Dart_NewStringFromUTF8(
reinterpret_cast<const uint8_t*>(current_dir_string.data()),
current_dir_string.length());
return Dart_Invoke(builtin_lib,
Dart_NewStringFromCString("_setWorkingDirectory"),
kNumArgs,
dart_args);
}
static Dart_Handle PrepareScriptForLoading(const std::string& package_root,
Dart_Handle builtin_lib) {
// First ensure all required libraries are available.
Dart_Handle url = Dart_NewStringFromCString(kInternalLibURL);
DART_CHECK_VALID(url);
Dart_Handle internal_lib = Dart_LookupLibrary(url);
DART_CHECK_VALID(internal_lib);
url = Dart_NewStringFromCString(kCoreLibURL);
DART_CHECK_VALID(url);
Dart_Handle core_lib = Dart_LookupLibrary(url);
DART_CHECK_VALID(internal_lib);
url = Dart_NewStringFromCString(kAsyncLibURL);
DART_CHECK_VALID(url);
Dart_Handle async_lib = Dart_LookupLibrary(url);
DART_CHECK_VALID(async_lib);
url = Dart_NewStringFromCString(kIsolateLibURL);
DART_CHECK_VALID(url);
Dart_Handle isolate_lib = Dart_LookupLibrary(url);
DART_CHECK_VALID(isolate_lib);
Dart_Handle mojo_internal_lib =
Builtin::GetLibrary(Builtin::kMojoInternalLibrary);
DART_CHECK_VALID(mojo_internal_lib);
// We need to ensure that all the scripts loaded so far are finalized
// as we are about to invoke some Dart code below to setup closures.
Dart_Handle result = Dart_FinalizeLoading(false);
DART_CHECK_VALID(result);
// Import dart:_internal into dart:mojo.builtin for setting up hooks.
result = Dart_LibraryImportLibrary(builtin_lib, internal_lib, Dart_Null());
DART_CHECK_VALID(result);
// Setup the internal library's 'internalPrint' function.
Dart_Handle print = Dart_Invoke(builtin_lib,
Dart_NewStringFromCString("_getPrintClosure"),
0,
nullptr);
DART_CHECK_VALID(print);
result = Dart_SetField(internal_lib,
Dart_NewStringFromCString("_printClosure"),
print);
DART_CHECK_VALID(result);
DART_CHECK_VALID(Dart_Invoke(
builtin_lib, Dart_NewStringFromCString("_setupHooks"), 0, nullptr));
DART_CHECK_VALID(Dart_Invoke(
isolate_lib, Dart_NewStringFromCString("_setupHooks"), 0, nullptr));
// Setup the 'scheduleImmediate' closure.
Dart_Handle schedule_immediate_closure = Dart_Invoke(
isolate_lib,
Dart_NewStringFromCString("_getIsolateScheduleImmediateClosure"),
0,
nullptr);
Dart_Handle schedule_args[1];
schedule_args[0] = schedule_immediate_closure;
result = Dart_Invoke(
async_lib,
Dart_NewStringFromCString("_setScheduleImmediateClosure"),
1,
schedule_args);
DART_CHECK_VALID(result);
// Set current working directory.
result = SetWorkingDirectory(builtin_lib);
if (Dart_IsError(result)) {
return result;
}
// Set up package root.
result = Dart_NewStringFromUTF8(
reinterpret_cast<const uint8_t*>(package_root.c_str()),
package_root.length());
DART_CHECK_VALID(result);
const int kNumArgs = 1;
Dart_Handle dart_args[kNumArgs];
dart_args[0] = result;
result = Dart_Invoke(builtin_lib,
Dart_NewStringFromCString("_setPackageRoot"),
kNumArgs,
dart_args);
DART_CHECK_VALID(result);
// Setup the uriBase with the base uri of the mojo app.
Dart_Handle uri_base = Dart_Invoke(
builtin_lib,
Dart_NewStringFromCString("_getUriBaseClosure"),
0,
nullptr);
DART_CHECK_VALID(uri_base);
result = Dart_SetField(core_lib,
Dart_NewStringFromCString("_uriBaseClosure"),
uri_base);
DART_CHECK_VALID(result);
return result;
}
static Dart_Isolate CreateServiceIsolateHelper(const char* script_uri,
char** error) {
// TODO(johnmccutchan): Add support the service isolate.
// No callbacks for service isolate.
IsolateCallbacks callbacks;
IsolateData* isolate_data =
new IsolateData(nullptr, false, callbacks, "", "", "");
Dart_Isolate isolate = Dart_CreateIsolate(script_uri,
"main",
isolate_snapshot_buffer,
isolate_data,
error);
if (isolate == nullptr) {
delete isolate_data;
return nullptr;
}
return isolate;
}
static Dart_Isolate CreateIsolateHelper(void* dart_app,
bool strict_compilation,
IsolateCallbacks callbacks,
const std::string& script,
const std::string& script_uri,
const std::string& package_root,
char** error) {
IsolateData* isolate_data = new IsolateData(
dart_app,
strict_compilation,
callbacks,
script,
script_uri,
package_root);
Dart_Isolate isolate = Dart_CreateIsolate(
script_uri.c_str(), "main", isolate_snapshot_buffer, isolate_data, error);
if (isolate == nullptr) {
delete isolate_data;
return nullptr;
}
DPCHECK(!Dart_IsServiceIsolate(isolate));
Dart_EnterScope();
Dart_IsolateSetStrictCompilation(strict_compilation);
// Setup the native resolvers for the builtin libraries as they are not set
// up when the snapshot is read.
CHECK(isolate_snapshot_buffer != nullptr);
Builtin::PrepareLibrary(Builtin::kBuiltinLibrary);
Builtin::PrepareLibrary(Builtin::kMojoInternalLibrary);
if (!callbacks.create.is_null()) {
callbacks.create.Run(script_uri.c_str(),
"main",
package_root.c_str(),
isolate_data,
error);
}
// Set up the library tag handler for this isolate.
Dart_Handle result = Dart_SetLibraryTagHandler(LibraryTagHandler);
DART_CHECK_VALID(result);
// Prepare builtin and its dependent libraries for use to resolve URIs.
// The builtin library is part of the snapshot and is already available.
Dart_Handle builtin_lib =
Builtin::GetLibrary(Builtin::kBuiltinLibrary);
DART_CHECK_VALID(builtin_lib);
result = PrepareScriptForLoading(package_root, builtin_lib);
DART_CHECK_VALID(result);
Dart_Handle uri = Dart_NewStringFromUTF8(
reinterpret_cast<const uint8_t*>(script_uri.c_str()),
script_uri.length());
DART_CHECK_VALID(uri);
const void* data = static_cast<const void*>(script.data());
Dart_Handle script_source = Dart_NewExternalTypedData(
Dart_TypedData_kUint8,
const_cast<void*>(data),
script.length());
DART_CHECK_VALID(script_source);
result = LoadDataAsync_Invoke(
Dart_Null(), uri, Dart_Null(), builtin_lib, script_source);
DART_CHECK_VALID(result);
// Run event-loop and wait for script loading to complete.
result = Dart_RunLoop();
ReportScriptError(result);
// Make the isolate runnable so that it is ready to handle messages.
Dart_ExitScope();
Dart_ExitIsolate();
bool retval = Dart_IsolateMakeRunnable(isolate);
if (!retval) {
*error = strdup("Invalid isolate state - Unable to make it runnable");
Dart_EnterIsolate(isolate);
Dart_ShutdownIsolate();
return nullptr;
}
return isolate;
}
static Dart_Isolate IsolateCreateCallback(const char* script_uri,
const char* main,
const char* package_root,
void* callback_data,
char** error) {
IsolateData* parent_isolate_data =
reinterpret_cast<IsolateData*>(callback_data);
std::string script_uri_string;
std::string package_root_string;
if (IsServiceIsolateURL(script_uri)) {
return CreateServiceIsolateHelper(script_uri, error);
}
if (script_uri == nullptr) {
if (callback_data == nullptr) {
*error = strdup("Invalid 'callback_data' - Unable to spawn new isolate");
return nullptr;
}
script_uri_string = parent_isolate_data->script_uri;
} else {
script_uri_string = std::string(script_uri);
}
if (package_root == nullptr) {
package_root_string = parent_isolate_data->package_root;
} else {
package_root_string = std::string(package_root);
}
return CreateIsolateHelper(parent_isolate_data->app,
parent_isolate_data->strict_compilation,
parent_isolate_data->callbacks,
parent_isolate_data->script,
script_uri_string,
package_root_string,
error);
}
static void IsolateShutdownCallback(void* callback_data) {
IsolateData* isolate_data = reinterpret_cast<IsolateData*>(callback_data);
if (!isolate_data->callbacks.shutdown.is_null()) {
isolate_data->callbacks.shutdown.Run(callback_data);
}
delete isolate_data;
}
static void UnhandledExceptionCallback(Dart_Handle error) {
Dart_Isolate isolate = Dart_CurrentIsolate();
void* data = Dart_IsolateData(isolate);
IsolateData* isolate_data = reinterpret_cast<IsolateData*>(data);
if (!isolate_data->callbacks.exception.is_null()) {
// TODO(zra): Instead of passing an error handle, it may make life easier
// for clients if we pass any error string here instead.
isolate_data->callbacks.exception.Run(error);
}
// Close handles generated by the isolate.
std::set<MojoHandle>& handles = isolate_data->unclosed_handles;
for (auto it = handles.begin(); it != handles.end(); ++it) {
MojoClose((*it));
}
handles.clear();
}
bool DartController::initialized_ = false;
Dart_Isolate DartController::root_isolate_ = nullptr;
bool DartController::strict_compilation_ = false;
void DartController::InitVmIfNeeded(Dart_EntropySource entropy,
const char** arguments,
int arguments_count) {
// TODO(zra): If runDartScript can be called from multiple threads
// concurrently, then initialized_ will need to be protected by a lock.
if (initialized_) {
return;
}
const int kNumArgs = arguments_count + 1;
const char* args[kNumArgs];
// TODO(zra): Fix Dart VM Shutdown race.
// There is a bug in Dart VM shutdown which causes its thread pool threads
// to potentially fail to exit when the rest of the VM is going down. This
// results in a segfault if they begin running again after the Dart
// embedder has been unloaded. Setting this flag to 0 ensures that these
// threads sleep forever instead of waking up and trying to run code
// that isn't there anymore.
args[0] = "--worker-timeout-millis=0";
for (int i = 0; i < arguments_count; ++i) {
args[i + 1] = arguments[i];
}
bool result = Dart_SetVMFlags(kNumArgs, args);
CHECK(result);
result = Dart_Initialize(vm_isolate_snapshot_buffer,
IsolateCreateCallback,
nullptr, // Isolate interrupt callback.
UnhandledExceptionCallback,
IsolateShutdownCallback,
// File IO callbacks.
nullptr, nullptr, nullptr, nullptr,
entropy);
CHECK(result);
initialized_ = true;
}
bool DartController::RunSingleDartScript(const DartControllerConfig& config) {
InitVmIfNeeded(config.entropy,
config.arguments,
config.arguments_count);
Dart_Isolate isolate = CreateIsolateHelper(config.application_data,
config.strict_compilation,
config.callbacks,
config.script,
config.script_uri,
config.package_root,
config.error);
if (isolate == nullptr) {
return false;
}
Dart_EnterIsolate(isolate);
Dart_Handle result;
Dart_EnterScope();
// Start the MojoHandleWatcher.
Dart_Handle mojo_internal_lib =
Builtin::GetLibrary(Builtin::kMojoInternalLibrary);
DART_CHECK_VALID(mojo_internal_lib);
Dart_Handle handle_watcher_type =
Dart_GetType(mojo_internal_lib,
Dart_NewStringFromCString("MojoHandleWatcher"), 0, nullptr);
DART_CHECK_VALID(handle_watcher_type);
result = Dart_Invoke(
handle_watcher_type,
Dart_NewStringFromCString("_start"),
0,
nullptr);
DART_CHECK_VALID(result);
// RunLoop until the handle watcher isolate is spun-up.
result = Dart_RunLoop();
DART_CHECK_VALID(result);
// Load the root library into the builtin library so that main can be found.
Dart_Handle builtin_lib =
Builtin::GetLibrary(Builtin::kBuiltinLibrary);
DART_CHECK_VALID(builtin_lib);
Dart_Handle root_lib = Dart_RootLibrary();
DART_CHECK_VALID(root_lib);
result = Dart_LibraryImportLibrary(builtin_lib, root_lib, Dart_Null());
DART_CHECK_VALID(result);
if (config.compile_all) {
result = Dart_CompileAll();
DART_CHECK_VALID(result);
}
Dart_Handle main_closure = Dart_Invoke(
builtin_lib,
Dart_NewStringFromCString("_getMainClosure"),
0,
nullptr);
DART_CHECK_VALID(main_closure);
// Call _startIsolate in the isolate library to enable dispatching the
// initial startup message.
const intptr_t kNumIsolateArgs = 2;
Dart_Handle isolate_args[kNumIsolateArgs];
isolate_args[0] = main_closure; // entryPoint
isolate_args[1] = Dart_NewList(2); // args
DART_CHECK_VALID(isolate_args[1]);
Dart_Handle script_uri = Dart_NewStringFromUTF8(
reinterpret_cast<const uint8_t*>(config.script_uri.data()),
config.script_uri.length());
Dart_ListSetAt(isolate_args[1], 0, Dart_NewInteger(config.handle));
Dart_ListSetAt(isolate_args[1], 1, script_uri);
Dart_Handle isolate_lib =
Dart_LookupLibrary(Dart_NewStringFromCString(kIsolateLibURL));
DART_CHECK_VALID(isolate_lib);
result = Dart_Invoke(isolate_lib,
Dart_NewStringFromCString("_startMainIsolate"),
kNumIsolateArgs,
isolate_args);
DART_CHECK_VALID(result);
result = Dart_RunLoop();
DART_CHECK_VALID(result);
// Stop the MojoHandleWatcher.
result = Dart_Invoke(
handle_watcher_type,
Dart_NewStringFromCString("_stop"),
0,
nullptr);
DART_CHECK_VALID(result);
result = Dart_RunLoop();
DART_CHECK_VALID(result);
Dart_ExitScope();
Dart_ShutdownIsolate();
Dart_Cleanup();
return true;
}
static bool generateEntropy(uint8_t* buffer, intptr_t length) {
crypto::RandBytes(reinterpret_cast<void*>(buffer), length);
return true;
}
bool DartController::Initialize(bool strict_compilation) {
char* error;
strict_compilation_ = strict_compilation;
// No callbacks for root isolate.
IsolateCallbacks callbacks;
InitVmIfNeeded(generateEntropy, nullptr, 0);
root_isolate_ = CreateIsolateHelper(
nullptr, false, callbacks, "", "", "", &error);
if (root_isolate_ == nullptr) {
LOG(ERROR) << error;
Dart_Cleanup();
initialized_ = false;
return false;
}
Dart_EnterIsolate(root_isolate_);
Dart_Handle result;
Dart_EnterScope();
// Start the MojoHandleWatcher.
Dart_Handle mojo_internal_lib =
Builtin::GetLibrary(Builtin::kMojoInternalLibrary);
DART_CHECK_VALID(mojo_internal_lib);
Dart_Handle handle_watcher_type =
Dart_GetType(mojo_internal_lib,
Dart_NewStringFromCString("MojoHandleWatcher"), 0, nullptr);
DART_CHECK_VALID(handle_watcher_type);
result = Dart_Invoke(
handle_watcher_type,
Dart_NewStringFromCString("_start"),
0,
nullptr);
DART_CHECK_VALID(result);
// RunLoop until the handle watcher isolate is spun-up.
result = Dart_RunLoop();
DART_CHECK_VALID(result);
Dart_ExitScope();
Dart_ExitIsolate();
return true;
}
bool DartController::RunDartScript(const DartControllerConfig& config) {
CHECK(root_isolate_ != nullptr);
const bool strict = strict_compilation_ || config.strict_compilation;
Dart_Isolate isolate = CreateIsolateHelper(
config.application_data, strict, config.callbacks, config.script,
config.script_uri, config.package_root, config.error);
if (isolate == nullptr) {
return false;
}
Dart_EnterIsolate(isolate);
Dart_Handle result;
Dart_EnterScope();
// Load the root library into the builtin library so that main can be found.
Dart_Handle builtin_lib =
Builtin::GetLibrary(Builtin::kBuiltinLibrary);
DART_CHECK_VALID(builtin_lib);
Dart_Handle root_lib = Dart_RootLibrary();
DART_CHECK_VALID(root_lib);
result = Dart_LibraryImportLibrary(builtin_lib, root_lib, Dart_Null());
DART_CHECK_VALID(result);
if (config.compile_all) {
result = Dart_CompileAll();
DART_CHECK_VALID(result);
}
Dart_Handle main_closure = Dart_Invoke(
builtin_lib,
Dart_NewStringFromCString("_getMainClosure"),
0,
nullptr);
DART_CHECK_VALID(main_closure);
// Call _startIsolate in the isolate library to enable dispatching the
// initial startup message.
const intptr_t kNumIsolateArgs = 2;
Dart_Handle isolate_args[kNumIsolateArgs];
isolate_args[0] = main_closure; // entryPoint
isolate_args[1] = Dart_NewList(2); // args
DART_CHECK_VALID(isolate_args[1]);
Dart_Handle script_uri = Dart_NewStringFromUTF8(
reinterpret_cast<const uint8_t*>(config.script_uri.data()),
config.script_uri.length());
Dart_ListSetAt(isolate_args[1], 0, Dart_NewInteger(config.handle));
Dart_ListSetAt(isolate_args[1], 1, script_uri);
Dart_Handle isolate_lib =
Dart_LookupLibrary(Dart_NewStringFromCString(kIsolateLibURL));
DART_CHECK_VALID(isolate_lib);
result = Dart_Invoke(isolate_lib,
Dart_NewStringFromCString("_startMainIsolate"),
kNumIsolateArgs,
isolate_args);
DART_CHECK_VALID(result);
// Run main until completion.
result = Dart_RunLoop();
ReportScriptError(result);
Dart_ExitScope();
Dart_ShutdownIsolate();
return true;
}
void DartController::Shutdown() {
CHECK(root_isolate_ != nullptr);
Dart_EnterIsolate(root_isolate_);
Dart_Handle result;
Dart_EnterScope();
// Stop the MojoHandleWatcher.
Dart_Handle mojo_internal_lib =
Builtin::GetLibrary(Builtin::kMojoInternalLibrary);
DART_CHECK_VALID(mojo_internal_lib);
Dart_Handle handle_watcher_type =
Dart_GetType(mojo_internal_lib,
Dart_NewStringFromCString("MojoHandleWatcher"), 0, nullptr);
DART_CHECK_VALID(handle_watcher_type);
result = Dart_Invoke(
handle_watcher_type,
Dart_NewStringFromCString("_stop"),
0,
nullptr);
DART_CHECK_VALID(result);
result = Dart_RunLoop();
DART_CHECK_VALID(result);
Dart_ExitScope();
Dart_ShutdownIsolate();
Dart_Cleanup();
root_isolate_ = nullptr;
initialized_ = false;
}
} // namespace apps
} // namespace mojo