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 "flutter/runtime/dart_vm_initializer.h" |
6 | |
7 | #include <atomic> |
8 | |
9 | #include "flutter/fml/logging.h" |
10 | #include "flutter/fml/synchronization/shared_mutex.h" |
11 | #include "flutter/fml/trace_event.h" |
12 | #include "flutter/lib/ui/ui_dart_state.h" |
13 | #include "flutter/lib/ui/window/platform_configuration.h" |
14 | #include "third_party/tonic/converter/dart_converter.h" |
15 | #include "third_party/tonic/logging/dart_error.h" |
16 | |
17 | #include "dart_timestamp_provider.h" |
18 | |
19 | namespace { |
20 | // Tracks whether Dart has been initialized and if it is safe to call Dart |
21 | // APIs. |
22 | static std::atomic<bool> gDartInitialized; |
23 | |
24 | void LogUnhandledException(Dart_Handle exception_handle, |
25 | Dart_Handle stack_trace_handle) { |
26 | const std::string error = |
27 | tonic::StdStringFromDart(handle: Dart_ToString(object: exception_handle)); |
28 | const std::string stack_trace = |
29 | tonic::StdStringFromDart(handle: Dart_ToString(object: stack_trace_handle)); |
30 | |
31 | auto state = flutter::UIDartState::Current(); |
32 | if (state && state->unhandled_exception_callback()) { |
33 | auto callback = state->unhandled_exception_callback(); |
34 | if (callback(error, stack_trace)) { |
35 | return; |
36 | } |
37 | } |
38 | |
39 | // Either the exception handler was not set or it could not handle the |
40 | // error, just log the exception. |
41 | FML_LOG(ERROR) << "Unhandled Exception: " << error << std::endl |
42 | << stack_trace; |
43 | } |
44 | |
45 | void ReportUnhandledException(Dart_Handle exception_handle, |
46 | Dart_Handle stack_trace_handle) { |
47 | // Hooks.dart will call the error handler on PlatformDispatcher if it is |
48 | // not null. If it is null, returns false, fall into the !handled branch |
49 | // below and log. |
50 | // If it is not null, defer to the return value of that closure |
51 | // to determine whether to report via logging. |
52 | bool handled = false; |
53 | auto state = flutter::UIDartState::Current(); |
54 | if (!state || !state->platform_configuration()) { |
55 | LogUnhandledException(exception_handle, stack_trace_handle); |
56 | return; |
57 | } |
58 | auto on_error = state->platform_configuration()->on_error(); |
59 | if (on_error) { |
60 | FML_DCHECK(!Dart_IsNull(on_error)); |
61 | Dart_Handle args[2]; |
62 | args[0] = exception_handle; |
63 | args[1] = stack_trace_handle; |
64 | Dart_Handle on_error_result = Dart_InvokeClosure(closure: on_error, number_of_arguments: 2, arguments: args); |
65 | |
66 | // An exception was thrown by the exception handler. |
67 | if (Dart_IsError(handle: on_error_result)) { |
68 | LogUnhandledException(exception_handle: Dart_ErrorGetException(handle: on_error_result), |
69 | stack_trace_handle: Dart_ErrorGetStackTrace(handle: on_error_result)); |
70 | |
71 | handled = false; |
72 | } else { |
73 | handled = tonic::DartConverter<bool>::FromDart(handle: on_error_result); |
74 | } |
75 | if (!handled) { |
76 | LogUnhandledException(exception_handle, stack_trace_handle); |
77 | } |
78 | } |
79 | } |
80 | } // namespace |
81 | |
82 | void DartVMInitializer::Initialize(Dart_InitializeParams* params, |
83 | bool enable_timeline_event_handler, |
84 | bool trace_systrace) { |
85 | FML_DCHECK(!gDartInitialized); |
86 | |
87 | char* error = Dart_Initialize(params); |
88 | if (error) { |
89 | FML_LOG(FATAL) << "Error while initializing the Dart VM: " << error; |
90 | ::free(ptr: error); |
91 | } else { |
92 | gDartInitialized = true; |
93 | } |
94 | |
95 | if (enable_timeline_event_handler) { |
96 | if (!trace_systrace) { |
97 | // Systrace on all platforms except Fuchsia ignores the timestamp provided |
98 | // here. On Android in particular, calls to get the system clock show up |
99 | // in profiles. |
100 | // Fuchsia does not use the TraceSetTimelineMicrosSource. |
101 | fml::tracing::TraceSetTimelineMicrosSource(source: Dart_TimelineGetMicros); |
102 | } else { |
103 | fml::tracing::TraceSetTimelineMicrosSource( |
104 | source: []() -> int64_t { return -1; }); |
105 | } |
106 | fml::tracing::TraceSetTimelineEventHandler(handler: LogDartTimelineEvent); |
107 | } |
108 | |
109 | fml::TimePoint::SetClockSource(flutter::DartTimelineTicksSinceEpoch); |
110 | tonic::SetUnhandledExceptionReporter(&ReportUnhandledException); |
111 | } |
112 | |
113 | void DartVMInitializer::Cleanup() { |
114 | FML_DCHECK(gDartInitialized); |
115 | |
116 | // Dart_RecordTimelineEvent is unsafe during a concurrent call to Dart_Cleanup |
117 | // because Dart_Cleanup will destroy the timeline recorder. Clear the |
118 | // initialized flag so that future calls to LogDartTimelineEvent will not |
119 | // call Dart_RecordTimelineEvent. |
120 | // |
121 | // Note that this is inherently racy. If a thread sees that gDartInitialized |
122 | // is set and proceeds to call Dart_RecordTimelineEvent shortly before another |
123 | // thread calls Dart_Cleanup, then the Dart_RecordTimelineEvent call may crash |
124 | // if Dart_Cleanup deletes the timeline before Dart_RecordTimelineEvent |
125 | // completes. In practice this is unlikely because Dart_Cleanup does |
126 | // significant other work before deleting the timeline. |
127 | // |
128 | // The engine can not safely guard Dart_Cleanup and LogDartTimelineEvent with |
129 | // a lock due to the risk of deadlocks. Dart_Cleanup waits for various |
130 | // Dart-owned threads to shut down. If one of those threads invokes an engine |
131 | // callback that calls LogDartTimelineEvent while the Dart_Cleanup thread owns |
132 | // the lock, then Dart_Cleanup would deadlock. |
133 | gDartInitialized = false; |
134 | |
135 | char* error = Dart_Cleanup(); |
136 | if (error) { |
137 | FML_LOG(FATAL) << "Error while cleaning up the Dart VM: " << error; |
138 | ::free(ptr: error); |
139 | } |
140 | } |
141 | |
142 | void DartVMInitializer::LogDartTimelineEvent(const char* label, |
143 | int64_t timestamp0, |
144 | int64_t timestamp1_or_async_id, |
145 | intptr_t flow_id_count, |
146 | const int64_t* flow_ids, |
147 | Dart_Timeline_Event_Type type, |
148 | intptr_t argument_count, |
149 | const char** argument_names, |
150 | const char** argument_values) { |
151 | if (gDartInitialized) { |
152 | Dart_RecordTimelineEvent(label, timestamp0, timestamp1_or_id: timestamp1_or_async_id, |
153 | flow_id_count, flow_ids, type, argument_count, |
154 | argument_names, argument_values); |
155 | } |
156 | } |
157 | |