1 // Copyright 2013 The Chromium 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 "gin/v8_initializer.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
10 #include <memory>
11 
12 #include "base/check.h"
13 #include "base/debug/alias.h"
14 #include "base/debug/crash_logging.h"
15 #include "base/feature_list.h"
16 #include "base/files/file.h"
17 #include "base/files/file_path.h"
18 #include "base/files/memory_mapped_file.h"
19 #include "base/lazy_instance.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/notreached.h"
22 #include "base/path_service.h"
23 #include "base/rand_util.h"
24 #include "base/strings/sys_string_conversions.h"
25 #include "base/system/sys_info.h"
26 #include "base/threading/platform_thread.h"
27 #include "base/time/time.h"
28 #include "build/build_config.h"
29 #include "gin/gin_features.h"
30 
31 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
32 #if defined(OS_ANDROID)
33 #include "base/android/apk_assets.h"
34 #elif defined(OS_MAC)
35 #include "base/mac/foundation_util.h"
36 #endif
37 #endif  // V8_USE_EXTERNAL_STARTUP_DATA
38 
39 namespace gin {
40 
41 namespace {
42 
43 // This global is never freed nor closed.
44 base::MemoryMappedFile* g_mapped_snapshot = nullptr;
45 
GenerateEntropy(unsigned char * buffer,size_t amount)46 bool GenerateEntropy(unsigned char* buffer, size_t amount) {
47   base::RandBytes(buffer, amount);
48   return true;
49 }
50 
GetMappedFileData(base::MemoryMappedFile * mapped_file,v8::StartupData * data)51 void GetMappedFileData(base::MemoryMappedFile* mapped_file,
52                        v8::StartupData* data) {
53   if (mapped_file) {
54     data->data = reinterpret_cast<const char*>(mapped_file->data());
55     data->raw_size = static_cast<int>(mapped_file->length());
56   } else {
57     data->data = nullptr;
58     data->raw_size = 0;
59   }
60 }
61 
62 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
63 
64 #if defined(OS_ANDROID)
65 const char kV8ContextSnapshotFileName64[] = "v8_context_snapshot_64.bin";
66 const char kV8ContextSnapshotFileName32[] = "v8_context_snapshot_32.bin";
67 const char kSnapshotFileName64[] = "snapshot_blob_64.bin";
68 const char kSnapshotFileName32[] = "snapshot_blob_32.bin";
69 
70 #if defined(__LP64__)
71 #define kV8ContextSnapshotFileName kV8ContextSnapshotFileName64
72 #define kSnapshotFileName kSnapshotFileName64
73 #else
74 #define kV8ContextSnapshotFileName kV8ContextSnapshotFileName32
75 #define kSnapshotFileName kSnapshotFileName32
76 #endif
77 
78 #else  // defined(OS_ANDROID)
79 #if defined(USE_V8_CONTEXT_SNAPSHOT)
80 const char kV8ContextSnapshotFileName[] = V8_CONTEXT_SNAPSHOT_FILENAME;
81 #endif
82 const char kSnapshotFileName[] = "snapshot_blob.bin";
83 #endif  // defined(OS_ANDROID)
84 
GetSnapshotFileName(const V8Initializer::V8SnapshotFileType file_type)85 const char* GetSnapshotFileName(
86     const V8Initializer::V8SnapshotFileType file_type) {
87   switch (file_type) {
88     case V8Initializer::V8SnapshotFileType::kDefault:
89       return kSnapshotFileName;
90     case V8Initializer::V8SnapshotFileType::kWithAdditionalContext:
91 #if defined(USE_V8_CONTEXT_SNAPSHOT)
92       return kV8ContextSnapshotFileName;
93 #else
94       NOTREACHED();
95       return nullptr;
96 #endif
97   }
98   NOTREACHED();
99   return nullptr;
100 }
101 
GetV8FilePath(const char * file_name,base::FilePath * path_out)102 void GetV8FilePath(const char* file_name, base::FilePath* path_out) {
103 #if defined(OS_ANDROID)
104   // This is the path within the .apk.
105   *path_out =
106       base::FilePath(FILE_PATH_LITERAL("assets")).AppendASCII(file_name);
107 #elif defined(OS_MAC)
108   base::ScopedCFTypeRef<CFStringRef> bundle_resource(
109       base::SysUTF8ToCFStringRef(file_name));
110   *path_out = base::mac::PathForFrameworkBundleResource(bundle_resource);
111 #else
112   base::FilePath data_path;
113   bool r = base::PathService::Get(base::DIR_ASSETS, &data_path);
114   DCHECK(r);
115   *path_out = data_path.AppendASCII(file_name);
116 #endif
117 }
118 
MapV8File(base::File file,base::MemoryMappedFile::Region region,base::MemoryMappedFile ** mmapped_file_out)119 bool MapV8File(base::File file,
120                base::MemoryMappedFile::Region region,
121                base::MemoryMappedFile** mmapped_file_out) {
122   DCHECK(*mmapped_file_out == NULL);
123   std::unique_ptr<base::MemoryMappedFile> mmapped_file(
124       new base::MemoryMappedFile());
125   if (mmapped_file->Initialize(std::move(file), region)) {
126     *mmapped_file_out = mmapped_file.release();
127     return true;
128   }
129   return false;
130 }
131 
OpenV8File(const char * file_name,base::MemoryMappedFile::Region * region_out)132 base::File OpenV8File(const char* file_name,
133                       base::MemoryMappedFile::Region* region_out) {
134   // Re-try logic here is motivated by http://crbug.com/479537
135   // for A/V on Windows (https://support.microsoft.com/en-us/kb/316609).
136 
137   // These match tools/metrics/histograms.xml
138   enum OpenV8FileResult {
139     OPENED = 0,
140     OPENED_RETRY,
141     FAILED_IN_USE,
142     FAILED_OTHER,
143     MAX_VALUE
144   };
145   base::FilePath path;
146   GetV8FilePath(file_name, &path);
147 
148 #if defined(OS_ANDROID)
149   base::File file(base::android::OpenApkAsset(path.value(), region_out));
150   OpenV8FileResult result = file.IsValid() ? OpenV8FileResult::OPENED
151                                            : OpenV8FileResult::FAILED_OTHER;
152 #else
153   // Re-try logic here is motivated by http://crbug.com/479537
154   // for A/V on Windows (https://support.microsoft.com/en-us/kb/316609).
155   const int kMaxOpenAttempts = 5;
156   const int kOpenRetryDelayMillis = 250;
157 
158   OpenV8FileResult result = OpenV8FileResult::FAILED_IN_USE;
159   int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
160   base::File file;
161   for (int attempt = 0; attempt < kMaxOpenAttempts; attempt++) {
162     file.Initialize(path, flags);
163     if (file.IsValid()) {
164       *region_out = base::MemoryMappedFile::Region::kWholeFile;
165       if (attempt == 0) {
166         result = OpenV8FileResult::OPENED;
167         break;
168       } else {
169         result = OpenV8FileResult::OPENED_RETRY;
170         break;
171       }
172     } else if (file.error_details() != base::File::FILE_ERROR_IN_USE) {
173       result = OpenV8FileResult::FAILED_OTHER;
174       break;
175     } else if (kMaxOpenAttempts - 1 != attempt) {
176       base::PlatformThread::Sleep(
177           base::TimeDelta::FromMilliseconds(kOpenRetryDelayMillis));
178     }
179   }
180 #endif  // defined(OS_ANDROID)
181 
182   UMA_HISTOGRAM_ENUMERATION("V8.Initializer.OpenV8File.Result",
183                             result,
184                             OpenV8FileResult::MAX_VALUE);
185   return file;
186 }
187 
188 enum LoadV8FileResult {
189   V8_LOAD_SUCCESS = 0,
190   V8_LOAD_FAILED_OPEN,
191   V8_LOAD_FAILED_MAP,
192   V8_LOAD_FAILED_VERIFY,  // Deprecated.
193   V8_LOAD_MAX_VALUE
194 };
195 
196 #endif  // defined(V8_USE_EXTERNAL_STARTUP_DATA)
197 
198 }  // namespace
199 
200 // static
Initialize(IsolateHolder::ScriptMode mode)201 void V8Initializer::Initialize(IsolateHolder::ScriptMode mode) {
202   static bool v8_is_initialized = false;
203   if (v8_is_initialized)
204     return;
205 
206   v8::V8::InitializePlatform(V8Platform::Get());
207 
208   if (!base::FeatureList::IsEnabled(features::kV8OptimizeJavascript)) {
209     // We avoid explicitly passing --opt if kV8OptimizeJavascript is enabled
210     // since it is the default, and doing so would override flags passed
211     // explicitly, e.g., via --js-flags=--no-opt.
212     static const char no_optimize[] = "--no-opt";
213     v8::V8::SetFlagsFromString(no_optimize, sizeof(no_optimize) - 1);
214   }
215 
216   if (!base::FeatureList::IsEnabled(features::kV8FlushBytecode)) {
217     static const char no_flush_bytecode[] = "--no-flush-bytecode";
218     v8::V8::SetFlagsFromString(no_flush_bytecode,
219                                sizeof(no_flush_bytecode) - 1);
220   }
221 
222   if (base::FeatureList::IsEnabled(features::kV8OffThreadFinalization)) {
223     static const char finalize_streaming_on_background[] =
224         "--finalize-streaming-on-background";
225     v8::V8::SetFlagsFromString(finalize_streaming_on_background,
226                                sizeof(finalize_streaming_on_background) - 1);
227   }
228 
229   if (!base::FeatureList::IsEnabled(features::kV8LazyFeedbackAllocation)) {
230     static const char no_lazy_feedback_allocation[] =
231         "--no-lazy-feedback-allocation";
232     v8::V8::SetFlagsFromString(no_lazy_feedback_allocation,
233                                sizeof(no_lazy_feedback_allocation) - 1);
234   }
235 
236   if (base::FeatureList::IsEnabled(features::kV8ConcurrentInlining)) {
237     static const char tf_experiment_concurrent_inlining[] =
238         "--concurrent_inlining";
239     v8::V8::SetFlagsFromString(tf_experiment_concurrent_inlining,
240                                sizeof(tf_experiment_concurrent_inlining) - 1);
241   }
242 
243   if (base::FeatureList::IsEnabled(features::kV8PerContextMarkingWorklist)) {
244     static const char stress_per_context_marking_worklist[] =
245         "--stress-per-context-marking-worklist";
246     v8::V8::SetFlagsFromString(stress_per_context_marking_worklist,
247                                sizeof(stress_per_context_marking_worklist) - 1);
248   }
249 
250   if (base::FeatureList::IsEnabled(features::kV8FlushEmbeddedBlobICache)) {
251     static const char experimental_flush_embedded_blob_icache[] =
252         "--experimental-flush-embedded-blob-icache";
253     v8::V8::SetFlagsFromString(
254         experimental_flush_embedded_blob_icache,
255         sizeof(experimental_flush_embedded_blob_icache) - 1);
256   }
257 
258   if (base::FeatureList::IsEnabled(features::kV8ReduceConcurrentMarkingTasks)) {
259     static const char gc_experiment_reduce_concurrent_marking_tasks[] =
260         "--gc-experiment-reduce-concurrent-marking-tasks";
261     v8::V8::SetFlagsFromString(
262         gc_experiment_reduce_concurrent_marking_tasks,
263         sizeof(gc_experiment_reduce_concurrent_marking_tasks) - 1);
264   }
265 
266   if (base::FeatureList::IsEnabled(features::kV8NoReclaimUnmodifiedWrappers)) {
267     static constexpr char no_reclaim_unmodified_wrappers[] =
268         "--no-reclaim-unmodified-wrappers";
269     v8::V8::SetFlagsFromString(no_reclaim_unmodified_wrappers,
270                                sizeof(no_reclaim_unmodified_wrappers) - 1);
271   }
272 
273   if (!base::FeatureList::IsEnabled(features::kV8LocalHeaps)) {
274     // The --local-heaps flag is enabled by default, so we need to explicitly
275     // disable it if kV8LocalHeaps is disabled.
276     static constexpr char no_local_heaps[] = "--no-local-heaps";
277     v8::V8::SetFlagsFromString(no_local_heaps, sizeof(no_local_heaps) - 1);
278 
279     // Also disable TurboFan's direct access if local heaps are not enabled.
280     static constexpr char no_direct_access[] = "--no-turbo-direct-heap-access";
281     v8::V8::SetFlagsFromString(no_direct_access, sizeof(no_direct_access) - 1);
282   }
283 
284   if (!base::FeatureList::IsEnabled(features::kV8TurboDirectHeapAccess)) {
285     // The --turbo-direct-heap-access flag is enabled by default, so we need to
286     // explicitly disable it if kV8TurboDirectHeapAccess is disabled.
287     static constexpr char no_direct_access[] = "--no-turbo-direct-heap-access";
288     v8::V8::SetFlagsFromString(no_direct_access, sizeof(no_direct_access) - 1);
289   }
290 
291   if (IsolateHolder::kStrictMode == mode) {
292     static const char use_strict[] = "--use_strict";
293     v8::V8::SetFlagsFromString(use_strict, sizeof(use_strict) - 1);
294   }
295 
296 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
297   if (g_mapped_snapshot) {
298     v8::StartupData snapshot;
299     GetMappedFileData(g_mapped_snapshot, &snapshot);
300     v8::V8::SetSnapshotDataBlob(&snapshot);
301   }
302 #endif  // V8_USE_EXTERNAL_STARTUP_DATA
303 
304   v8::V8::SetEntropySource(&GenerateEntropy);
305   v8::V8::Initialize();
306 
307   v8_is_initialized = true;
308 }
309 
310 // static
GetV8ExternalSnapshotData(v8::StartupData * snapshot)311 void V8Initializer::GetV8ExternalSnapshotData(v8::StartupData* snapshot) {
312   GetMappedFileData(g_mapped_snapshot, snapshot);
313 }
314 
315 // static
GetV8ExternalSnapshotData(const char ** snapshot_data_out,int * snapshot_size_out)316 void V8Initializer::GetV8ExternalSnapshotData(const char** snapshot_data_out,
317                                               int* snapshot_size_out) {
318   v8::StartupData snapshot;
319   GetV8ExternalSnapshotData(&snapshot);
320   *snapshot_data_out = snapshot.data;
321   *snapshot_size_out = snapshot.raw_size;
322 }
323 
324 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
325 
326 // static
LoadV8Snapshot(V8SnapshotFileType snapshot_file_type)327 void V8Initializer::LoadV8Snapshot(V8SnapshotFileType snapshot_file_type) {
328   if (g_mapped_snapshot) {
329     // TODO(crbug.com/802962): Confirm not loading different type of snapshot
330     // files in a process.
331     return;
332   }
333 
334   base::MemoryMappedFile::Region file_region;
335   base::File file =
336       OpenV8File(GetSnapshotFileName(snapshot_file_type), &file_region);
337   LoadV8SnapshotFromFile(std::move(file), &file_region, snapshot_file_type);
338 }
339 
340 // static
LoadV8SnapshotFromFile(base::File snapshot_file,base::MemoryMappedFile::Region * snapshot_file_region,V8SnapshotFileType snapshot_file_type)341 void V8Initializer::LoadV8SnapshotFromFile(
342     base::File snapshot_file,
343     base::MemoryMappedFile::Region* snapshot_file_region,
344     V8SnapshotFileType snapshot_file_type) {
345   if (g_mapped_snapshot)
346     return;
347 
348   if (!snapshot_file.IsValid()) {
349     UMA_HISTOGRAM_ENUMERATION("V8.Initializer.LoadV8Snapshot.Result",
350                               V8_LOAD_FAILED_OPEN, V8_LOAD_MAX_VALUE);
351     return;
352   }
353 
354   base::MemoryMappedFile::Region region =
355       base::MemoryMappedFile::Region::kWholeFile;
356   if (snapshot_file_region) {
357     region = *snapshot_file_region;
358   }
359 
360   LoadV8FileResult result = V8_LOAD_SUCCESS;
361   if (!MapV8File(std::move(snapshot_file), region, &g_mapped_snapshot))
362     result = V8_LOAD_FAILED_MAP;
363   UMA_HISTOGRAM_ENUMERATION("V8.Initializer.LoadV8Snapshot.Result", result,
364                             V8_LOAD_MAX_VALUE);
365 }
366 
367 #if defined(OS_ANDROID)
368 // static
GetSnapshotFilePath(bool abi_32_bit,V8SnapshotFileType snapshot_file_type)369 base::FilePath V8Initializer::GetSnapshotFilePath(
370     bool abi_32_bit,
371     V8SnapshotFileType snapshot_file_type) {
372   base::FilePath path;
373   const char* filename = nullptr;
374   switch (snapshot_file_type) {
375     case V8Initializer::V8SnapshotFileType::kDefault:
376       filename = abi_32_bit ? kSnapshotFileName32 : kSnapshotFileName64;
377       break;
378     case V8Initializer::V8SnapshotFileType::kWithAdditionalContext:
379       filename = abi_32_bit ? kV8ContextSnapshotFileName32
380                             : kV8ContextSnapshotFileName64;
381       break;
382   }
383   CHECK(filename);
384 
385   GetV8FilePath(filename, &path);
386   return path;
387 }
388 #endif  // defined(OS_ANDROID)
389 #endif  // defined(V8_USE_EXTERNAL_STARTUP_DATA)
390 
391 }  // namespace gin
392