• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "component.h"
6 
7 #include <dlfcn.h>
8 #include <fuchsia/mem/cpp/fidl.h>
9 #include <lib/async-loop/cpp/loop.h>
10 #include <lib/async/cpp/task.h>
11 #include <lib/fdio/directory.h>
12 #include <lib/fdio/namespace.h>
13 #include <lib/ui/scenic/cpp/view_token_pair.h>
14 #include <lib/vfs/cpp/remote_dir.h>
15 #include <lib/vfs/cpp/service.h>
16 #include <sys/stat.h>
17 #include <zircon/dlfcn.h>
18 #include <zircon/status.h>
19 #include <regex>
20 #include <sstream>
21 
22 #include "flutter/fml/synchronization/waitable_event.h"
23 #include "flutter/runtime/dart_vm_lifecycle.h"
24 #include "flutter/shell/common/switches.h"
25 #include "runtime/dart/utils/files.h"
26 #include "runtime/dart/utils/handle_exception.h"
27 #include "runtime/dart/utils/tempfs.h"
28 #include "runtime/dart/utils/vmo.h"
29 
30 #include "service_provider_dir.h"
31 #include "task_observers.h"
32 #include "task_runner_adapter.h"
33 #include "thread.h"
34 
35 namespace flutter_runner {
36 
37 constexpr char kDataKey[] = "data";
38 constexpr char kTmpPath[] = "/tmp";
39 constexpr char kServiceRootPath[] = "/svc";
40 
41 std::pair<std::unique_ptr<Thread>, std::unique_ptr<Application>>
Create(TerminationCallback termination_callback,fuchsia::sys::Package package,fuchsia::sys::StartupInfo startup_info,std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller)42 Application::Create(
43     TerminationCallback termination_callback,
44     fuchsia::sys::Package package,
45     fuchsia::sys::StartupInfo startup_info,
46     std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
47     fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
48   std::unique_ptr<Thread> thread = std::make_unique<Thread>();
49   std::unique_ptr<Application> application;
50 
51   fml::AutoResetWaitableEvent latch;
52   async::PostTask(thread->dispatcher(), [&]() mutable {
53     application.reset(
54         new Application(std::move(termination_callback), std::move(package),
55                         std::move(startup_info), runner_incoming_services,
56                         std::move(controller)));
57     latch.Signal();
58   });
59 
60   latch.Wait();
61   return {std::move(thread), std::move(application)};
62 }
63 
DebugLabelForURL(const std::string & url)64 static std::string DebugLabelForURL(const std::string& url) {
65   auto found = url.rfind("/");
66   if (found == std::string::npos) {
67     return url;
68   } else {
69     return {url, found + 1};
70   }
71 }
72 
Application(TerminationCallback termination_callback,fuchsia::sys::Package package,fuchsia::sys::StartupInfo startup_info,std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,fidl::InterfaceRequest<fuchsia::sys::ComponentController> application_controller_request)73 Application::Application(
74     TerminationCallback termination_callback,
75     fuchsia::sys::Package package,
76     fuchsia::sys::StartupInfo startup_info,
77     std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
78     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
79         application_controller_request)
80     : termination_callback_(std::move(termination_callback)),
81       debug_label_(DebugLabelForURL(startup_info.launch_info.url)),
82       application_controller_(this),
83       outgoing_dir_(new vfs::PseudoDir()),
84       runner_incoming_services_(runner_incoming_services),
85       weak_factory_(this) {
86   application_controller_.set_error_handler(
87       [this](zx_status_t status) { Kill(); });
88 
89   FML_DCHECK(fdio_ns_.is_valid());
90   // LaunchInfo::url non-optional.
91   auto& launch_info = startup_info.launch_info;
92 
93   // LaunchInfo::arguments optional.
94   if (auto& arguments = launch_info.arguments) {
95     settings_ = flutter::SettingsFromCommandLine(
96         fml::CommandLineFromIterators(arguments->begin(), arguments->end()));
97   }
98 
99   // Determine /pkg/data directory from StartupInfo.
100   std::string data_path;
101   for (size_t i = 0; i < startup_info.program_metadata->size(); ++i) {
102     auto pg = startup_info.program_metadata->at(i);
103     if (pg.key.compare(kDataKey) == 0) {
104       data_path = "pkg/" + pg.value;
105     }
106   }
107   if (data_path.empty()) {
108     FML_DLOG(ERROR) << "Could not find a /pkg/data directory for "
109                     << package.resolved_url;
110     return;
111   }
112 
113   // Setup /tmp to be mapped to the process-local memfs.
114   dart_utils::SetupComponentTemp(fdio_ns_.get());
115 
116   // LaunchInfo::flat_namespace optional.
117   for (size_t i = 0; i < startup_info.flat_namespace.paths.size(); ++i) {
118     const auto& path = startup_info.flat_namespace.paths.at(i);
119     if (path == kTmpPath) {
120       continue;
121     }
122 
123     zx::channel dir;
124     if (path == kServiceRootPath) {
125       svc_ = std::make_unique<sys::ServiceDirectory>(
126           std::move(startup_info.flat_namespace.directories.at(i)));
127       dir = svc_->CloneChannel().TakeChannel();
128     } else {
129       dir = std::move(startup_info.flat_namespace.directories.at(i));
130     }
131 
132     zx_handle_t dir_handle = dir.release();
133     if (fdio_ns_bind(fdio_ns_.get(), path.data(), dir_handle) != ZX_OK) {
134       FML_DLOG(ERROR) << "Could not bind path to namespace: " << path;
135       zx_handle_close(dir_handle);
136     }
137   }
138 
139   application_directory_.reset(fdio_ns_opendir(fdio_ns_.get()));
140   FML_DCHECK(application_directory_.is_valid());
141 
142   application_assets_directory_.reset(openat(
143       application_directory_.get(), data_path.c_str(), O_RDONLY | O_DIRECTORY));
144 
145   // TODO: LaunchInfo::out.
146 
147   // TODO: LaunchInfo::err.
148 
149   // LaunchInfo::service_request optional.
150   if (launch_info.directory_request) {
151     outgoing_dir_->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
152                              fuchsia::io::OPEN_RIGHT_WRITABLE |
153                              fuchsia::io::OPEN_FLAG_DIRECTORY,
154                          std::move(launch_info.directory_request));
155   }
156 
157   directory_request_ = directory_ptr_.NewRequest();
158 
159   fidl::InterfaceHandle<fuchsia::io::Directory> flutter_public_dir;
160   // TODO(anmittal): when fixing enumeration using new c++ vfs, make sure that
161   // flutter_public_dir is only accessed once we receive OnOpen Event.
162   // That will prevent FL-175 for public directory
163   auto request = flutter_public_dir.NewRequest().TakeChannel();
164   fdio_service_connect_at(directory_ptr_.channel().get(), "svc",
165                           request.release());
166 
167   auto service_provider_dir = std::make_unique<ServiceProviderDir>();
168   service_provider_dir->set_fallback(std::move(flutter_public_dir));
169 
170   // Clone and check if client is servicing the directory.
171   directory_ptr_->Clone(fuchsia::io::OPEN_FLAG_DESCRIBE |
172                             fuchsia::io::OPEN_RIGHT_READABLE |
173                             fuchsia::io::OPEN_RIGHT_WRITABLE,
174                         cloned_directory_ptr_.NewRequest());
175 
176   cloned_directory_ptr_.events().OnOpen =
177       [this](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) {
178         cloned_directory_ptr_.Unbind();
179         if (status != ZX_OK) {
180           FML_LOG(ERROR) << "could not bind out directory for flutter app("
181                          << debug_label_
182                          << "): " << zx_status_get_string(status);
183           return;
184         }
185         const char* other_dirs[] = {"debug", "ctrl"};
186         // add other directories as RemoteDirs.
187         for (auto& dir_str : other_dirs) {
188           fidl::InterfaceHandle<fuchsia::io::Directory> dir;
189           auto request = dir.NewRequest().TakeChannel();
190           fdio_service_connect_at(directory_ptr_.channel().get(), dir_str,
191                                   request.release());
192           outgoing_dir_->AddEntry(
193               dir_str, std::make_unique<vfs::RemoteDir>(dir.TakeChannel()));
194         }
195       };
196 
197   cloned_directory_ptr_.set_error_handler(
198       [this](zx_status_t status) { cloned_directory_ptr_.Unbind(); });
199 
200   // TODO: LaunchInfo::additional_services optional.
201 
202   // All launch arguments have been read. Perform service binding and
203   // final settings configuration. The next call will be to create a view
204   // for this application.
205   service_provider_dir->AddService(
206       fuchsia::ui::app::ViewProvider::Name_,
207       std::make_unique<vfs::Service>(
208           [this](zx::channel channel, async_dispatcher_t* dispatcher) {
209             shells_bindings_.AddBinding(
210                 this, fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider>(
211                           std::move(channel)));
212           }));
213 
214   outgoing_dir_->AddEntry("svc", std::move(service_provider_dir));
215 
216   // Setup the application controller binding.
217   if (application_controller_request) {
218     application_controller_.Bind(std::move(application_controller_request));
219   }
220 
221   // Compare flutter_jit_runner in BUILD.gn.
222   settings_.vm_snapshot_data_path = "pkg/data/vm_snapshot_data.bin";
223   settings_.vm_snapshot_instr_path = "pkg/data/vm_snapshot_instructions.bin";
224   settings_.isolate_snapshot_data_path =
225       "pkg/data/isolate_core_snapshot_data.bin";
226   settings_.isolate_snapshot_instr_path =
227       "pkg/data/isolate_core_snapshot_instructions.bin";
228 
229   {
230     // Check if we can use the snapshot with the framework already loaded.
231     std::string runner_framework;
232     std::string app_framework;
233     if (dart_utils::ReadFileToString("pkg/data/runner.frameworkversion",
234                                      &runner_framework) &&
235         dart_utils::ReadFileToStringAt(application_assets_directory_.get(),
236                                        "app.frameworkversion",
237                                        &app_framework) &&
238         (runner_framework.compare(app_framework) == 0)) {
239       settings_.vm_snapshot_data_path =
240           "pkg/data/framework_vm_snapshot_data.bin";
241       settings_.vm_snapshot_instr_path =
242           "pkg/data/framework_vm_snapshot_instructions.bin";
243       settings_.isolate_snapshot_data_path =
244           "pkg/data/framework_isolate_core_snapshot_data.bin";
245       settings_.isolate_snapshot_instr_path =
246           "pkg/data/framework_isolate_core_snapshot_instructions.bin";
247 
248       FML_LOG(INFO) << "Using snapshot with framework for "
249                     << package.resolved_url;
250     } else {
251       FML_LOG(INFO) << "Using snapshot without framework for "
252                     << package.resolved_url;
253     }
254   }
255 
256 #if defined(DART_PRODUCT)
257   settings_.enable_observatory = false;
258 #else
259   settings_.enable_observatory = true;
260 #endif
261 
262   settings_.icu_data_path = "";
263 
264   settings_.assets_dir = application_assets_directory_.get();
265 
266   // Compare flutter_jit_app in flutter_app.gni.
267   settings_.application_kernel_list_asset = "app.dilplist";
268 
269   settings_.log_tag = debug_label_ + std::string{"(flutter)"};
270 
271   // No asserts in debug or release product.
272   // No asserts in release with flutter_profile=true (non-product)
273   // Yes asserts in non-product debug.
274 #if !defined(DART_PRODUCT) && (!defined(FLUTTER_PROFILE) || !defined(NDEBUG))
275   // Debug mode
276   settings_.disable_dart_asserts = false;
277 #else
278   // Release mode
279   settings_.disable_dart_asserts = true;
280 #endif
281 
282   settings_.task_observer_add =
283       std::bind(&CurrentMessageLoopAddAfterTaskObserver, std::placeholders::_1,
284                 std::placeholders::_2);
285 
286   settings_.task_observer_remove = std::bind(
287       &CurrentMessageLoopRemoveAfterTaskObserver, std::placeholders::_1);
288 
289   // TODO(FL-117): Re-enable causal async stack traces when this issue is
290   // addressed.
291   settings_.dart_flags = {"--no_causal_async_stacks"};
292 
293   // Disable code collection as it interferes with JIT code warmup
294   // by decreasing usage counters and flushing code which is still useful.
295   settings_.dart_flags.push_back("--no-collect_code");
296 
297   if (!flutter::DartVM::IsRunningPrecompiledCode()) {
298     // The interpreter is enabled unconditionally in JIT mode. If an app is
299     // built for debugging (that is, with no bytecode), the VM will fall back on
300     // ASTs.
301     settings_.dart_flags.push_back("--enable_interpreter");
302   }
303 
304   // Don't collect CPU samples from Dart VM C++ code.
305   settings_.dart_flags.push_back("--no_profile_vm");
306 
307   // Scale back CPU profiler sampling period on ARM64 to avoid overloading
308   // the tracing engine.
309 #if defined(__aarch64__)
310   settings_.dart_flags.push_back("--profile_period=10000");
311 #endif  // defined(__aarch64__)
312 
313   auto weak_application = weak_factory_.GetWeakPtr();
314   auto platform_task_runner =
315       CreateFMLTaskRunner(async_get_default_dispatcher());
316   const std::string component_url = package.resolved_url;
317   settings_.unhandled_exception_callback =
318       [weak_application, platform_task_runner, runner_incoming_services,
319        component_url](const std::string& error,
320                       const std::string& stack_trace) {
321         if (weak_application) {
322           // TODO(cbracken): unsafe. The above check and the PostTask below are
323           // happening on the UI thread. If the Application dtor and thread
324           // termination happen (on the platform thread) between the previous
325           // line and the next line, a crash will occur since we'll be posting
326           // to a dead thread. See Runner::OnApplicationTerminate() in
327           // runner.cc.
328           platform_task_runner->PostTask([weak_application,
329                                           runner_incoming_services,
330                                           component_url, error, stack_trace]() {
331             if (weak_application) {
332               dart_utils::HandleException(runner_incoming_services,
333                                           component_url, error, stack_trace);
334             } else {
335               FML_LOG(ERROR)
336                   << "Unhandled exception after application shutdown: "
337                   << error;
338             }
339           });
340         } else {
341           FML_LOG(ERROR) << "Unhandled exception after application shutdown: "
342                          << error;
343         }
344         // Ideally we would return whether HandleException returned ZX_OK, but
345         // short of knowing if the exception was correctly handled, we return
346         // false to have the error and stack trace printed in the logs.
347         return false;
348       };
349 
350   AttemptVMLaunchWithCurrentSettings(settings_);
351 }
352 
353 Application::~Application() = default;
354 
GetDebugLabel() const355 const std::string& Application::GetDebugLabel() const {
356   return debug_label_;
357 }
358 
359 class FileInNamespaceBuffer final : public fml::Mapping {
360  public:
FileInNamespaceBuffer(int namespace_fd,const char * path,bool executable)361   FileInNamespaceBuffer(int namespace_fd, const char* path, bool executable)
362       : address_(nullptr), size_(0) {
363     fuchsia::mem::Buffer buffer;
364     if (!dart_utils::VmoFromFilenameAt(namespace_fd, path, &buffer)) {
365       return;
366     }
367     if (buffer.size == 0) {
368       return;
369     }
370 
371     uint32_t flags = ZX_VM_PERM_READ;
372     if (executable) {
373       flags |= ZX_VM_PERM_EXECUTE;
374 
375       // VmoFromFilenameAt will return VMOs without ZX_RIGHT_EXECUTE,
376       // so we need replace_as_executable to be able to map them as
377       // ZX_VM_PERM_EXECUTE.
378       // TODO(mdempsky): Update comment once SEC-42 is fixed.
379       zx_status_t status =
380           buffer.vmo.replace_as_executable(zx::handle(), &buffer.vmo);
381       if (status != ZX_OK) {
382         FML_LOG(FATAL) << "Failed to make VMO executable: "
383                        << zx_status_get_string(status);
384       }
385     }
386     uintptr_t addr;
387     zx_status_t status =
388         zx::vmar::root_self()->map(0, buffer.vmo, 0, buffer.size, flags, &addr);
389     if (status != ZX_OK) {
390       FML_LOG(FATAL) << "Failed to map " << path << ": "
391                      << zx_status_get_string(status);
392     }
393 
394     address_ = reinterpret_cast<void*>(addr);
395     size_ = buffer.size;
396   }
397 
~FileInNamespaceBuffer()398   ~FileInNamespaceBuffer() {
399     if (address_ != nullptr) {
400       zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(address_),
401                                    size_);
402       address_ = nullptr;
403       size_ = 0;
404     }
405   }
406 
407   // |fml::Mapping|
GetMapping() const408   const uint8_t* GetMapping() const override {
409     return reinterpret_cast<const uint8_t*>(address_);
410   }
411 
412   // |fml::Mapping|
GetSize() const413   size_t GetSize() const override { return size_; }
414 
415  private:
416   void* address_;
417   size_t size_;
418 
419   FML_DISALLOW_COPY_AND_ASSIGN(FileInNamespaceBuffer);
420 };
421 
CreateWithContentsOfFile(int namespace_fd,const char * file_path,bool executable)422 std::unique_ptr<fml::Mapping> CreateWithContentsOfFile(int namespace_fd,
423                                                        const char* file_path,
424                                                        bool executable) {
425   FML_TRACE_EVENT("flutter", "LoadFile", "path", file_path);
426   auto source = std::make_unique<FileInNamespaceBuffer>(namespace_fd, file_path,
427                                                         executable);
428   return source->GetMapping() == nullptr ? nullptr : std::move(source);
429 }
430 
AttemptVMLaunchWithCurrentSettings(const flutter::Settings & settings)431 void Application::AttemptVMLaunchWithCurrentSettings(
432     const flutter::Settings& settings) {
433   if (!flutter::DartVM::IsRunningPrecompiledCode()) {
434     // We will be initializing the VM lazily in this case.
435     return;
436   }
437 
438   // Compare flutter_aot_app in flutter_app.gni.
439   fml::RefPtr<flutter::DartSnapshot> vm_snapshot =
440       fml::MakeRefCounted<flutter::DartSnapshot>(
441           CreateWithContentsOfFile(
442               application_assets_directory_.get() /* /pkg/data */,
443               "vm_snapshot_data.bin", false),
444           CreateWithContentsOfFile(
445               application_assets_directory_.get() /* /pkg/data */,
446               "vm_snapshot_instructions.bin", true));
447 
448   isolate_snapshot_ = fml::MakeRefCounted<flutter::DartSnapshot>(
449       CreateWithContentsOfFile(
450           application_assets_directory_.get() /* /pkg/data */,
451           "isolate_snapshot_data.bin", false),
452       CreateWithContentsOfFile(
453           application_assets_directory_.get() /* /pkg/data */,
454           "isolate_snapshot_instructions.bin", true));
455 
456   shared_snapshot_ = fml::MakeRefCounted<flutter::DartSnapshot>(
457       CreateWithContentsOfFile(
458           application_assets_directory_.get() /* /pkg/data */,
459           "shared_snapshot_data.bin", false),
460       CreateWithContentsOfFile(
461           application_assets_directory_.get() /* /pkg/data */,
462           "shared_snapshot_instructions.bin", true));
463 
464   auto vm = flutter::DartVMRef::Create(settings_,               //
465                                        std::move(vm_snapshot),  //
466                                        isolate_snapshot_,       //
467                                        shared_snapshot_         //
468   );
469   FML_CHECK(vm) << "Mut be able to initialize the VM.";
470 }
471 
472 // |fuchsia::sys::ComponentController|
Kill()473 void Application::Kill() {
474   application_controller_.events().OnTerminated(
475       last_return_code_.second, fuchsia::sys::TerminationReason::EXITED);
476 
477   termination_callback_(this);
478   // WARNING: Don't do anything past this point as this instance may have been
479   // collected.
480 }
481 
482 // |fuchsia::sys::ComponentController|
Detach()483 void Application::Detach() {
484   application_controller_.set_error_handler(nullptr);
485 }
486 
487 // |flutter::Engine::Delegate|
OnEngineTerminate(const Engine * shell_holder)488 void Application::OnEngineTerminate(const Engine* shell_holder) {
489   auto found = std::find_if(shell_holders_.begin(), shell_holders_.end(),
490                             [shell_holder](const auto& holder) {
491                               return holder.get() == shell_holder;
492                             });
493 
494   if (found == shell_holders_.end()) {
495     return;
496   }
497 
498   // We may launch multiple shell in this application. However, we will
499   // terminate when the last shell goes away. The error code return to the
500   // application controller will be the last isolate that had an error.
501   auto return_code = shell_holder->GetEngineReturnCode();
502   if (return_code.first) {
503     last_return_code_ = return_code;
504   }
505 
506   shell_holders_.erase(found);
507 
508   if (shell_holders_.size() == 0) {
509     Kill();
510     // WARNING: Don't do anything past this point because the delegate may have
511     // collected this instance via the termination callback.
512   }
513 }
514 
515 // |fuchsia::ui::app::ViewProvider|
CreateView(zx::eventpair view_token,fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)516 void Application::CreateView(
517     zx::eventpair view_token,
518     fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
519     fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) {
520   if (!svc_) {
521     FML_DLOG(ERROR)
522         << "Component incoming services was invalid when attempting to "
523            "create a shell for a view provider request.";
524     return;
525   }
526 
527   shell_holders_.emplace(std::make_unique<Engine>(
528       *this,                         // delegate
529       debug_label_,                  // thread label
530       svc_,                          // Component incoming services
531       settings_,                     // settings
532       std::move(isolate_snapshot_),  // isolate snapshot
533       std::move(shared_snapshot_),   // shared snapshot
534       scenic::ToViewToken(std::move(view_token)),  // view token
535       std::move(fdio_ns_),                         // FDIO namespace
536       std::move(directory_request_)                // outgoing request
537       ));
538 }
539 
540 #if !defined(DART_PRODUCT)
WriteProfileToTrace() const541 void Application::WriteProfileToTrace() const {
542   for (const auto& engine : shell_holders_) {
543     engine->WriteProfileToTrace();
544   }
545 }
546 #endif  // !defined(DART_PRODUCT)
547 
548 }  // namespace flutter_runner
549