// 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 #include #include #include #include #include #include #include #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/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", }; 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 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(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(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(dart_state), error) : Dart_CreateIsolate( script_uri, "main", g_default_isolate_snapshot_data, g_default_isolate_snapshot_instructions, nullptr, static_cast(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 } 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 kernel_data; std::vector 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; fxl::RefPtr zip_asset_store = fxl::MakeRefCounted( GetUnzipperProviderForPath(std::move(bundle_path))); zip_asset_store->GetAsBuffer(kKernelAssetKey, &kernel_data); zip_asset_store->GetAsBuffer(kSnapshotAssetKey, &snapshot_data); } } UIDartState* parent_dart_state = static_cast(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 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::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 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 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* 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 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()) { auto zip_asset_store = fxl::MakeRefCounted( GetUnzipperProviderForPath(std::move(bundle_path))); zip_asset_store->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(¶ms); 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