blob: 4049fbb7fe7e342f60c3c5e8d0bee1976c4694d6 [file] [log] [blame]
// Copyright 2015 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 "flutter/runtime/dart_init.h"
#include "flutter/sky/engine/wtf/OperatingSystem.h"
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#if defined(OS_WIN)
#include <io.h>
#include <windows.h>
#undef ERROR
#define access _access
#define R_OK 0x4
#ifndef S_ISDIR
#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR)
#endif
#else
#include <unistd.h>
#endif
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "flutter/assets/directory_asset_bundle.h"
#include "flutter/assets/unzipper_provider.h"
#include "flutter/assets/zip_asset_store.h"
#include "flutter/common/settings.h"
#include "flutter/glue/trace_event.h"
#include "flutter/lib/io/dart_io.h"
#include "flutter/lib/ui/dart_runtime_hooks.h"
#include "flutter/lib/ui/dart_ui.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/window.h"
#include "flutter/runtime/dart_service_isolate.h"
#include "flutter/runtime/start_up.h"
#include "lib/fxl/arraysize.h"
#include "lib/fxl/build_config.h"
#include "lib/fxl/files/path.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/time/time_delta.h"
#include "lib/tonic/converter/dart_converter.h"
#include "lib/tonic/dart_class_library.h"
#include "lib/tonic/dart_state.h"
#include "lib/tonic/dart_sticky_error.h"
#include "lib/tonic/dart_wrappable.h"
#include "lib/tonic/file_loader/file_loader.h"
#include "lib/tonic/logging/dart_error.h"
#include "lib/tonic/logging/dart_invoke.h"
#include "lib/tonic/scopes/dart_api_scope.h"
#include "lib/tonic/scopes/dart_isolate_scope.h"
#include "lib/tonic/typed_data/uint8_list.h"
#include "third_party/dart/runtime/bin/embedded_dart_io.h"
#include "third_party/dart/runtime/include/dart_mirrors_api.h"
using tonic::DartClassProvider;
using tonic::LogIfError;
using tonic::ToDart;
namespace dart {
namespace observatory {
#if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE
// These two symbols are defined in |observatory_archive.cc| which is generated
// by the |//third_party/dart/runtime/observatory:archive_observatory| rule.
// Both of these symbols will be part of the data segment and therefore are read
// only.
extern unsigned int observatory_assets_archive_len;
extern const uint8_t* observatory_assets_archive;
#endif // FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE
} // namespace observatory
} // namespace dart
namespace blink {
const char kKernelAssetKey[] = "kernel_blob.bin";
const char kSnapshotAssetKey[] = "snapshot_blob.bin";
const char kPlatformKernelAssetKey[] = "platform.dill";
namespace {
// Arguments passed to the Dart VM in all configurations.
static const char* kDartLanguageArgs[] = {
"--enable_mirrors=false", "--background_compilation", "--await_is_keyword",
"--causal_async_stacks", "--limit-ints-to-64-bits",
};
static const char* kDartPrecompilationArgs[] = {
"--precompilation",
};
static const char* kDartWriteProtectCodeArgs[] FXL_ALLOW_UNUSED_TYPE = {
"--no_write_protect_code",
};
static const char* kDartCheckedModeArgs[] = {
// clang-format off
"--enable_asserts",
"--enable_type_checks",
"--error_on_bad_type",
"--error_on_bad_override",
// clang-format on
};
static const char* kDartStartPausedArgs[]{
"--pause_isolates_on_start",
};
static const char* kDartTraceStartupArgs[]{
"--timeline_streams=Compiler,Dart,Embedder,GC",
};
static const char* kDartEndlessTraceBufferArgs[]{
"--timeline_recorder=endless",
};
static const char* kDartFuchsiaTraceArgs[] FXL_ALLOW_UNUSED_TYPE = {
"--systrace_timeline",
"--timeline_streams=VM,Isolate,Compiler,Dart,GC",
};
constexpr char kFileUriPrefix[] = "file://";
constexpr size_t kFileUriPrefixLength = sizeof(kFileUriPrefix) - 1;
static const uint8_t* g_default_isolate_snapshot_data = nullptr;
static const uint8_t* g_default_isolate_snapshot_instructions = nullptr;
static bool g_service_isolate_initialized = false;
static ServiceIsolateHook g_service_isolate_hook = nullptr;
static RegisterNativeServiceProtocolExtensionHook
g_register_native_service_protocol_extensions_hook = nullptr;
// Kernel representation of core dart libraries(loaded from platform.dill).
// TODO(aam): This (and platform_data below) have to be released when engine
// gets torn down. At that point we could also call Dart_Cleanup to complete
// Dart VM cleanup.
static void* kernel_platform = nullptr;
// Bytes actually read from platform.dill that are referenced by kernel_platform
static std::vector<uint8_t> platform_data;
void IsolateShutdownCallback(void* callback_data) {
if (tonic::DartStickyError::IsSet()) {
tonic::DartApiScope api_scope;
FXL_LOG(ERROR) << "Isolate " << tonic::StdStringFromDart(Dart_DebugName())
<< " exited with an error";
Dart_Handle sticky_error = Dart_GetStickyError();
FXL_CHECK(LogIfError(sticky_error));
}
UIDartState* dart_state = static_cast<UIDartState*>(callback_data);
// If the isolate that's shutting down is the main one, tell the higher layers
// of the stack.
if ((dart_state != NULL) && dart_state->is_controller_state()) {
dart_state->set_shutting_down(true);
if (dart_state->isolate_client()) {
dart_state->isolate_client()->DidShutdownMainIsolate();
}
}
}
// The cleanup callback frees the DartState object.
void IsolateCleanupCallback(void* callback_data) {
UIDartState* dart_state = static_cast<UIDartState*>(callback_data);
delete dart_state;
}
bool DartFileModifiedCallback(const char* source_url, int64_t since_ms) {
if (strncmp(source_url, kFileUriPrefix, kFileUriPrefixLength) != 0u) {
// Assume modified.
return true;
}
const char* path = source_url + kFileUriPrefixLength;
struct stat info;
if (stat(path, &info) < 0)
return true;
// If st_mtime is zero, it's more likely that the file system doesn't support
// mtime than that the file was actually modified in the 1970s.
if (!info.st_mtime)
return true;
// It's very unclear what time bases we're with here. The Dart API doesn't
// document the time base for since_ms. Reading the code, the value varies by
// platform, with a typical source being something like gettimeofday.
//
// We add one to st_mtime because st_mtime has less precision than since_ms
// and we want to treat the file as modified if the since time is between
// ticks of the mtime.
fxl::TimeDelta mtime = fxl::TimeDelta::FromSeconds(info.st_mtime + 1);
fxl::TimeDelta since = fxl::TimeDelta::FromMilliseconds(since_ms);
return mtime > since;
}
void ThreadExitCallback() {}
bool IsServiceIsolateURL(const char* url_name) {
return url_name != nullptr &&
std::string(url_name) == DART_VM_SERVICE_ISOLATE_NAME;
}
static bool StringEndsWith(const std::string& string,
const std::string& ending) {
if (ending.size() > string.size())
return false;
return string.compare(string.size() - ending.size(), ending.size(), ending) ==
0;
}
static void ReleaseFetchedBytes(uint8_t* buffer) {
free(buffer);
}
Dart_Isolate ServiceIsolateCreateCallback(const char* script_uri,
char** error) {
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE
// No VM-service in release mode.
return nullptr;
#else // FLUTTER_RUNTIME_MODE
UIDartState* dart_state = new UIDartState(nullptr, nullptr);
bool is_running_from_kernel = GetKernelPlatformBinary() != nullptr;
Dart_Isolate isolate =
is_running_from_kernel
? Dart_CreateIsolateFromKernel(
script_uri, "main", kernel_platform, nullptr /* flags */,
static_cast<tonic::DartState*>(dart_state), error)
: Dart_CreateIsolate(
script_uri, "main", g_default_isolate_snapshot_data,
g_default_isolate_snapshot_instructions, nullptr,
static_cast<tonic::DartState*>(dart_state), error);
FXL_CHECK(isolate) << error;
dart_state->set_debug_name_prefix(script_uri);
dart_state->SetIsolate(isolate);
FXL_CHECK(Dart_IsServiceIsolate(isolate));
FXL_CHECK(!LogIfError(
Dart_SetLibraryTagHandler(tonic::DartState::HandleLibraryTag)));
{
tonic::DartApiScope dart_api_scope;
DartIO::InitForIsolate();
DartUI::InitForIsolate();
DartRuntimeHooks::Install(DartRuntimeHooks::SecondaryIsolate, script_uri);
const Settings& settings = Settings::Get();
if (settings.enable_observatory) {
std::string ip = settings.ipv6 ? "::1" : "127.0.0.1";
const intptr_t port = settings.observatory_port;
const bool disable_websocket_origin_check = false;
const bool service_isolate_booted = DartServiceIsolate::Startup(
ip, port, tonic::DartState::HandleLibraryTag,
!IsRunningPrecompiledCode() && !is_running_from_kernel,
disable_websocket_origin_check, error);
FXL_CHECK(service_isolate_booted) << error;
}
if (g_service_isolate_hook)
g_service_isolate_hook(IsRunningPrecompiledCode());
}
Dart_ExitIsolate();
g_service_isolate_initialized = true;
// Register any native service protocol extensions.
if (g_register_native_service_protocol_extensions_hook) {
g_register_native_service_protocol_extensions_hook(
IsRunningPrecompiledCode());
}
return isolate;
#endif // FLUTTER_RUNTIME_MODE
}
static bool GetAssetAsBuffer(
const std::string& name,
std::vector<uint8_t>* data,
std::unique_ptr<DirectoryAssetBundle>& directory_asset_bundle,
fxl::RefPtr<ZipAssetStore>& asset_store) {
return (directory_asset_bundle &&
directory_asset_bundle->GetAsBuffer(name, data)) ||
(asset_store && asset_store->GetAsBuffer(name, data));
}
Dart_Isolate IsolateCreateCallback(const char* script_uri,
const char* main,
const char* package_root,
const char* package_config,
Dart_IsolateFlags* flags,
void* callback_data,
char** error) {
TRACE_EVENT0("flutter", __func__);
if (IsServiceIsolateURL(script_uri)) {
return ServiceIsolateCreateCallback(script_uri, error);
}
std::string entry_uri = script_uri;
// Are we running from a Dart source file?
const bool running_from_source = StringEndsWith(entry_uri, ".dart");
std::vector<uint8_t> kernel_data;
std::vector<uint8_t> snapshot_data;
std::string entry_path;
if (!IsRunningPrecompiledCode()) {
// Check that the entry script URI starts with file://
if (entry_uri.find(kFileUriPrefix) != 0u) {
*error = strdup("Isolates must use file:// URIs");
return nullptr;
}
// Entry script path (file:// is stripped).
entry_path = std::string(script_uri + strlen(kFileUriPrefix));
if (!running_from_source) {
// Attempt to copy the snapshot from the asset bundle.
const std::string& bundle_path = entry_path;
struct stat stat_result = {};
if (::stat(bundle_path.c_str(), &stat_result) == 0) {
std::unique_ptr<DirectoryAssetBundle> directory_asset_bundle;
// TODO(zarah): Remove usage of zip_asset_store once app.flx is removed.
fxl::RefPtr<ZipAssetStore> zip_asset_store;
// bundle_path is either the path to app.flx or the flutter assets
// directory.
std::string flx_path = bundle_path;
if (S_ISDIR(stat_result.st_mode)) {
directory_asset_bundle =
std::make_unique<DirectoryAssetBundle>(bundle_path);
flx_path = files::GetDirectoryName(bundle_path) + "/app.flx";
}
if (access(flx_path.c_str(), R_OK) == 0) {
zip_asset_store = fxl::MakeRefCounted<ZipAssetStore>(
GetUnzipperProviderForPath(flx_path));
}
GetAssetAsBuffer(kKernelAssetKey, &kernel_data, directory_asset_bundle,
zip_asset_store);
GetAssetAsBuffer(kSnapshotAssetKey, &snapshot_data,
directory_asset_bundle, zip_asset_store);
}
}
}
UIDartState* parent_dart_state = static_cast<UIDartState*>(callback_data);
UIDartState* dart_state = parent_dart_state->CreateForChildIsolate();
Dart_Isolate isolate =
kernel_platform != nullptr
? Dart_CreateIsolateFromKernel(script_uri, main, kernel_platform,
nullptr /* flags */, dart_state, error)
: Dart_CreateIsolate(script_uri, main,
g_default_isolate_snapshot_data,
g_default_isolate_snapshot_instructions, nullptr,
dart_state, error);
FXL_CHECK(isolate) << error;
dart_state->set_debug_name_prefix(script_uri);
dart_state->SetIsolate(isolate);
FXL_CHECK(!LogIfError(
Dart_SetLibraryTagHandler(tonic::DartState::HandleLibraryTag)));
{
tonic::DartApiScope dart_api_scope;
DartIO::InitForIsolate();
DartUI::InitForIsolate();
DartRuntimeHooks::Install(DartRuntimeHooks::SecondaryIsolate, script_uri);
std::unique_ptr<DartClassProvider> ui_class_provider(
new DartClassProvider(dart_state, "dart:ui"));
dart_state->class_library().add_provider("ui",
std::move(ui_class_provider));
if (!kernel_data.empty()) {
// We are running kernel code.
FXL_CHECK(!LogIfError(Dart_LoadKernel(Dart_ReadKernelBinary(
kernel_data.data(), kernel_data.size(), ReleaseFetchedBytes))));
} else if (!snapshot_data.empty()) {
// We are running from a script snapshot.
FXL_CHECK(!LogIfError(Dart_LoadScriptFromSnapshot(snapshot_data.data(),
snapshot_data.size())));
} else if (running_from_source) {
// We are running from source.
// Forward the .packages configuration from the parent isolate to the
// child isolate.
tonic::FileLoader& parent_loader = parent_dart_state->file_loader();
const std::string& packages = parent_loader.packages();
tonic::FileLoader& loader = dart_state->file_loader();
if (!packages.empty() && !loader.LoadPackagesMap(packages)) {
FXL_LOG(WARNING) << "Failed to load package map: " << packages;
}
// Load the script.
FXL_CHECK(!LogIfError(loader.LoadScript(entry_path)));
}
dart_state->isolate_client()->DidCreateSecondaryIsolate(isolate);
}
Dart_ExitIsolate();
FXL_CHECK(Dart_IsolateMakeRunnable(isolate));
return isolate;
}
Dart_Handle GetVMServiceAssetsArchiveCallback() {
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE
return nullptr;
#else // FLUTTER_RUNTIME_MODE
return tonic::DartConverter<tonic::Uint8List>::ToDart(
::dart::observatory::observatory_assets_archive,
::dart::observatory::observatory_assets_archive_len);
#endif // FLUTTER_RUNTIME_MODE
}
static const char kStdoutStreamId[] = "Stdout";
static const char kStderrStreamId[] = "Stderr";
static bool ServiceStreamListenCallback(const char* stream_id) {
if (strcmp(stream_id, kStdoutStreamId) == 0) {
dart::bin::SetCaptureStdout(true);
return true;
} else if (strcmp(stream_id, kStderrStreamId) == 0) {
dart::bin::SetCaptureStderr(true);
return true;
}
return false;
}
static void ServiceStreamCancelCallback(const char* stream_id) {
if (strcmp(stream_id, kStdoutStreamId) == 0) {
dart::bin::SetCaptureStdout(false);
} else if (strcmp(stream_id, kStderrStreamId) == 0) {
dart::bin::SetCaptureStderr(false);
}
}
} // namespace
bool IsRunningPrecompiledCode() {
return Dart_IsPrecompiledRuntime();
}
EmbedderTracingCallbacks* g_tracing_callbacks = nullptr;
EmbedderTracingCallbacks::EmbedderTracingCallbacks(
EmbedderTracingCallback start,
EmbedderTracingCallback stop)
: start_tracing_callback(start), stop_tracing_callback(stop) {}
void SetEmbedderTracingCallbacks(
std::unique_ptr<EmbedderTracingCallbacks> callbacks) {
g_tracing_callbacks = callbacks.release();
}
static void EmbedderTimelineStartRecording() {
if (g_tracing_callbacks)
g_tracing_callbacks->start_tracing_callback();
}
static void EmbedderTimelineStopRecording() {
if (g_tracing_callbacks)
g_tracing_callbacks->stop_tracing_callback();
}
static std::vector<const char*> ProfilingFlags(bool enable_profiling) {
// Disable Dart's built in profiler when building a debug build. This
// works around a race condition that would sometimes stop a crash's
// stack trace from being printed on Android.
#ifndef NDEBUG
enable_profiling = false;
#endif
// We want to disable profiling by default because it overwhelms LLDB. But
// the VM enables the same by default. In either case, we have some profiling
// flags.
if (enable_profiling) {
return {
// Dart assumes ARM devices are insufficiently powerful and sets the
// default profile period to 100Hz. This number is suitable for older
// Raspberry Pi devices but quite low for current smartphones.
"--profile_period=1000",
// This is the default. But just be explicit.
"--profiler",
// This instructs the profiler to walk C++ frames, and to include
// them in the profile.
"--profile-vm"};
} else {
return {"--no-profiler"};
}
}
void SetServiceIsolateHook(ServiceIsolateHook hook) {
FXL_CHECK(!g_service_isolate_initialized);
g_service_isolate_hook = hook;
}
void SetRegisterNativeServiceProtocolExtensionHook(
RegisterNativeServiceProtocolExtensionHook hook) {
FXL_CHECK(!g_service_isolate_initialized);
g_register_native_service_protocol_extensions_hook = hook;
}
void PushBackAll(std::vector<const char*>* args,
const char** argv,
size_t argc) {
for (size_t i = 0; i < argc; ++i) {
args->push_back(argv[i]);
}
}
static void EmbedderInformationCallback(Dart_EmbedderInformation* info) {
info->version = DART_EMBEDDER_INFORMATION_CURRENT_VERSION;
dart::bin::GetIOEmbedderInformation(info);
info->name = "Flutter";
}
void* GetKernelPlatformBinary() {
return kernel_platform;
}
void InitDartVM(const uint8_t* vm_snapshot_data,
const uint8_t* vm_snapshot_instructions,
const uint8_t* default_isolate_snapshot_data,
const uint8_t* default_isolate_snapshot_instructions,
const std::string& bundle_path) {
TRACE_EVENT0("flutter", __func__);
g_default_isolate_snapshot_data = default_isolate_snapshot_data;
g_default_isolate_snapshot_instructions =
default_isolate_snapshot_instructions;
const Settings& settings = Settings::Get();
{
TRACE_EVENT0("flutter", "dart::bin::BootstrapDartIo");
dart::bin::BootstrapDartIo();
if (!settings.temp_directory_path.empty()) {
dart::bin::SetSystemTempDirectory(settings.temp_directory_path.c_str());
}
}
std::vector<const char*> args;
// Instruct the VM to ignore unrecognized flags.
// There is a lot of diversity in a lot of combinations when it
// comes to the arguments the VM supports. And, if the VM comes across a flag
// it does not recognize, it exits immediately.
args.push_back("--ignore-unrecognized-flags");
for (const auto& profiler_flag :
ProfilingFlags(settings.enable_dart_profiling)) {
args.push_back(profiler_flag);
}
PushBackAll(&args, kDartLanguageArgs, arraysize(kDartLanguageArgs));
if (IsRunningPrecompiledCode()) {
PushBackAll(&args, kDartPrecompilationArgs,
arraysize(kDartPrecompilationArgs));
}
#if defined(OS_FUCHSIA) && defined(NDEBUG)
// Do not enable checked mode for Fuchsia release builds
// TODO(mikejurka): remove this once precompiled code is working on Fuchsia
const bool use_checked_mode = false;
#else
// Enable checked mode if we are not running precompiled code. We run non-
// precompiled code only in the debug product mode.
const bool use_checked_mode =
!IsRunningPrecompiledCode() && !settings.dart_non_checked_mode;
#endif
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
// Debug mode uses the JIT, disable code page write protection to avoid
// memory page protection changes before and after every compilation.
PushBackAll(&args, kDartWriteProtectCodeArgs,
arraysize(kDartWriteProtectCodeArgs));
#endif
if (use_checked_mode)
PushBackAll(&args, kDartCheckedModeArgs, arraysize(kDartCheckedModeArgs));
if (settings.start_paused)
PushBackAll(&args, kDartStartPausedArgs, arraysize(kDartStartPausedArgs));
if (settings.endless_trace_buffer || settings.trace_startup) {
// If we are tracing startup, make sure the trace buffer is endless so we
// don't lose early traces.
PushBackAll(&args, kDartEndlessTraceBufferArgs,
arraysize(kDartEndlessTraceBufferArgs));
}
if (settings.trace_startup) {
PushBackAll(&args, kDartTraceStartupArgs, arraysize(kDartTraceStartupArgs));
}
#if defined(OS_FUCHSIA)
PushBackAll(&args, kDartFuchsiaTraceArgs, arraysize(kDartFuchsiaTraceArgs));
#endif
if (!bundle_path.empty()) {
fxl::RefPtr<blink::DirectoryAssetBundle> directory_asset_bundle =
fxl::MakeRefCounted<blink::DirectoryAssetBundle>(
std::move(bundle_path));
directory_asset_bundle->GetAsBuffer(kPlatformKernelAssetKey,
&platform_data);
if (!platform_data.empty()) {
kernel_platform = Dart_ReadKernelBinary(
platform_data.data(), platform_data.size(), ReleaseFetchedBytes);
FXL_DCHECK(kernel_platform != nullptr);
}
}
for (size_t i = 0; i < settings.dart_flags.size(); i++)
args.push_back(settings.dart_flags[i].c_str());
FXL_CHECK(Dart_SetVMFlags(args.size(), args.data()));
DartUI::InitForGlobal();
// Setup embedder tracing hooks. To avoid data races, it is recommended that
// these hooks be installed before the DartInitialize, so do that setup now.
Dart_SetEmbedderTimelineCallbacks(&EmbedderTimelineStartRecording,
&EmbedderTimelineStopRecording);
Dart_SetFileModifiedCallback(&DartFileModifiedCallback);
{
TRACE_EVENT0("flutter", "Dart_Initialize");
Dart_InitializeParams params = {};
params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
params.vm_snapshot_data = vm_snapshot_data;
params.vm_snapshot_instructions = vm_snapshot_instructions;
params.create = IsolateCreateCallback;
params.shutdown = IsolateShutdownCallback;
params.cleanup = IsolateCleanupCallback;
params.thread_exit = ThreadExitCallback;
params.get_service_assets = GetVMServiceAssetsArchiveCallback;
params.entropy_source = DartIO::EntropySource;
char* init_error = Dart_Initialize(&params);
if (init_error != nullptr)
FXL_LOG(FATAL) << "Error while initializing the Dart VM: " << init_error;
free(init_error);
// Send the earliest available timestamp in the application lifecycle to
// timeline. The difference between this timestamp and the time we render
// the very first frame gives us a good idea about Flutter's startup time.
// Use a duration event so about:tracing will consider this event when
// deciding the earliest event to use as time 0.
if (blink::engine_main_enter_ts != 0) {
Dart_TimelineEvent("FlutterEngineMainEnter", // label
blink::engine_main_enter_ts, // timestamp0
blink::engine_main_enter_ts, // timestamp1_or_async_id
Dart_Timeline_Event_Duration, // event type
0, // argument_count
nullptr, // argument_names
nullptr // argument_values
);
}
}
// Allow streaming of stdout and stderr by the Dart vm.
Dart_SetServiceStreamCallbacks(&ServiceStreamListenCallback,
&ServiceStreamCancelCallback);
Dart_SetEmbedderInformationCallback(&EmbedderInformationCallback);
}
} // namespace blink