Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(161)

Side by Side Diff: gin/v8_initializer.cc

Issue 1164483003: Allow startup with missing V8 snapshot file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add natives_fd_exists and snapshot_fd_exists instead of checking for -1 Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "gin/v8_initializer.h" 5 #include "gin/v8_initializer.h"
6 6
7 #include "base/basictypes.h" 7 #include "base/basictypes.h"
8 #include "base/files/file.h" 8 #include "base/files/file.h"
9 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
10 #include "base/files/memory_mapped_file.h" 10 #include "base/files/memory_mapped_file.h"
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 #endif // !OS_MACOSX 43 #endif // !OS_MACOSX
44 44
45 const char kNativesFileName[] = "natives_blob.bin"; 45 const char kNativesFileName[] = "natives_blob.bin";
46 const char kSnapshotFileName[] = "snapshot_blob.bin"; 46 const char kSnapshotFileName[] = "snapshot_blob.bin";
47 47
48 // Constants for snapshot loading retries taken from: 48 // Constants for snapshot loading retries taken from:
49 // https://support.microsoft.com/en-us/kb/316609. 49 // https://support.microsoft.com/en-us/kb/316609.
50 const int kMaxOpenAttempts = 5; 50 const int kMaxOpenAttempts = 5;
51 const int kOpenRetryDelayMillis = 250; 51 const int kOpenRetryDelayMillis = 250;
52 52
53 void GetV8FilePaths(base::FilePath* natives_path_out, 53 void GetV8FilePath(base::FilePath* path, const char* file_name) {
rmcilroy 2015/06/02 15:20:27 nit - /s/path/path_out and move file_name to first
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
54 base::FilePath* snapshot_path_out) {
55 #if !defined(OS_MACOSX) 54 #if !defined(OS_MACOSX)
56 base::FilePath data_path; 55 base::FilePath data_path;
57 PathService::Get(kV8SnapshotBasePathKey, &data_path); 56 PathService::Get(kV8SnapshotBasePathKey, &data_path);
58 DCHECK(!data_path.empty()); 57 DCHECK(!data_path.empty());
59 58
60 *natives_path_out = data_path.AppendASCII(kNativesFileName); 59 *path = data_path.AppendASCII(file_name);
61 *snapshot_path_out = data_path.AppendASCII(kSnapshotFileName);
62 #else // !defined(OS_MACOSX) 60 #else // !defined(OS_MACOSX)
63 base::ScopedCFTypeRef<CFStringRef> natives_file_name( 61 base::ScopedCFTypeRef<CFStringRef> natives_file_name(
64 base::SysUTF8ToCFStringRef(kNativesFileName)); 62 base::SysUTF8ToCFStringRef(file_name));
65 *natives_path_out = 63 *path = base::mac::PathForFrameworkBundleResource(natives_file_name);
66 base::mac::PathForFrameworkBundleResource(natives_file_name);
67 base::ScopedCFTypeRef<CFStringRef> snapshot_file_name(
68 base::SysUTF8ToCFStringRef(kSnapshotFileName));
69 *snapshot_path_out =
70 base::mac::PathForFrameworkBundleResource(snapshot_file_name);
71 DCHECK(!natives_path_out->empty());
72 DCHECK(!snapshot_path_out->empty());
73 #endif // !defined(OS_MACOSX) 64 #endif // !defined(OS_MACOSX)
65 DCHECK(!path->empty());
74 } 66 }
75 67
76 static bool MapV8Files(base::File natives_file, 68 bool MapV8File(base::MemoryMappedFile** mmapped_file_return,
rmcilroy 2015/06/02 15:20:27 nit - /s/mmapped_file_return/mmapped_file_out and
rmcilroy 2015/06/02 15:20:27 Keep as static?
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
77 base::File snapshot_file, 69 base::File file,
78 base::MemoryMappedFile::Region natives_region = 70 base::MemoryMappedFile::Region region =
79 base::MemoryMappedFile::Region::kWholeFile, 71 base::MemoryMappedFile::Region::kWholeFile) {
80 base::MemoryMappedFile::Region snapshot_region = 72 base::MemoryMappedFile* mmapped_file = *mmapped_file_return =
rmcilroy 2015/06/02 15:20:27 DCHECK(*mmapped_file_out == NULL) before allocatin
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
81 base::MemoryMappedFile::Region::kWholeFile) { 73 new base::MemoryMappedFile;
82 g_mapped_natives = new base::MemoryMappedFile; 74 if (!mmapped_file->IsValid()) {
rmcilroy 2015/06/02 15:20:27 I don't think this is necessary right? mmapped_fil
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
83 if (!g_mapped_natives->IsValid()) { 75 if (!mmapped_file->Initialize(file.Pass(), region)) {
84 if (!g_mapped_natives->Initialize(natives_file.Pass(), natives_region)) { 76 delete mmapped_file;
85 delete g_mapped_natives; 77 *mmapped_file_return = NULL;
86 g_mapped_natives = NULL;
87 LOG(FATAL) << "Couldn't mmap v8 natives data file";
88 return false; 78 return false;
89 } 79 }
90 } 80 }
91
92 g_mapped_snapshot = new base::MemoryMappedFile;
93 if (!g_mapped_snapshot->IsValid()) {
94 if (!g_mapped_snapshot->Initialize(snapshot_file.Pass(), snapshot_region)) {
95 delete g_mapped_snapshot;
96 g_mapped_snapshot = NULL;
97 LOG(ERROR) << "Couldn't mmap v8 snapshot data file";
98 return false;
99 }
100 }
101 81
102 return true; 82 return true;
103 } 83 }
104 84
105 static bool OpenV8File(const base::FilePath& path, 85 static bool OpenV8File(const base::FilePath& path,
106 int flags, 86 int flags,
107 base::File& file) { 87 base::File& file) {
108 // Re-try logic here is motivated by http://crbug.com/479537 88 // Re-try logic here is motivated by http://crbug.com/479537
109 // for A/V on Windows (https://support.microsoft.com/en-us/kb/316609). 89 // for A/V on Windows (https://support.microsoft.com/en-us/kb/316609).
110 90
(...skipping 28 matching lines...) Expand all
139 119
140 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.OpenV8File.Result", 120 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.OpenV8File.Result",
141 result, 121 result,
142 OpenV8FileResult::MAX_VALUE); 122 OpenV8FileResult::MAX_VALUE);
143 123
144 return result == OpenV8FileResult::OPENED 124 return result == OpenV8FileResult::OPENED
145 || result == OpenV8FileResult::OPENED_RETRY; 125 || result == OpenV8FileResult::OPENED_RETRY;
146 } 126 }
147 127
148 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA) 128 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA)
149 bool VerifyV8SnapshotFile(base::MemoryMappedFile* snapshot_file, 129 bool VerifyV8StartupFile(base::MemoryMappedFile* file,
150 const unsigned char* fingerprint) { 130 const unsigned char* fingerprint) {
151 unsigned char output[crypto::kSHA256Length]; 131 unsigned char output[crypto::kSHA256Length];
152 crypto::SHA256HashString( 132 crypto::SHA256HashString(
153 base::StringPiece(reinterpret_cast<const char*>(snapshot_file->data()), 133 base::StringPiece(reinterpret_cast<const char*>(file->data()),
154 snapshot_file->length()), 134 file->length()),
155 output, sizeof(output)); 135 output, sizeof(output));
156 return !memcmp(fingerprint, output, sizeof(output)); 136 return !memcmp(fingerprint, output, sizeof(output));
157 } 137 }
158 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA 138 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA
159 #endif // V8_USE_EXTERNAL_STARTUP_DATA 139 #endif // V8_USE_EXTERNAL_STARTUP_DATA
160 140
161 bool GenerateEntropy(unsigned char* buffer, size_t amount) { 141 bool GenerateEntropy(unsigned char* buffer, size_t amount) {
162 base::RandBytes(buffer, amount); 142 base::RandBytes(buffer, amount);
163 return true; 143 return true;
164 } 144 }
165 145
166 } // namespace 146 } // namespace
167 147
168 #if defined(V8_USE_EXTERNAL_STARTUP_DATA) 148 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
169 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA) 149 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA)
170 // Defined in gen/gin/v8_snapshot_fingerprint.cc 150 // Defined in gen/gin/v8_snapshot_fingerprint.cc
171 extern const unsigned char g_natives_fingerprint[]; 151 extern const unsigned char g_natives_fingerprint[];
172 extern const unsigned char g_snapshot_fingerprint[]; 152 extern const unsigned char g_snapshot_fingerprint[];
173 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA 153 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA
174 154
155 enum LoadV8FileResult {
156 V8_LOAD_SUCCESS = 0,
157 V8_LOAD_FAILED_OPEN,
158 V8_LOAD_FAILED_MAP,
159 V8_LOAD_FAILED_VERIFY,
160 V8_LOAD_MAX_VALUE
161 };
162
175 // static 163 // static
176 bool V8Initializer::LoadV8Snapshot() { 164 void V8Initializer::LoadV8Snapshot() {
165 if (g_mapped_snapshot)
166 return;
177 167
178 enum LoadV8SnapshotResult { 168 base::FilePath snapshot_data_path;
179 SUCCESS = 0, 169 GetV8FilePath(&snapshot_data_path, kSnapshotFileName);
180 FAILED_OPEN,
181 FAILED_MAP,
182 FAILED_VERIFY,
183 MAX_VALUE
184 };
185 170
186 if (g_mapped_natives && g_mapped_snapshot)
187 return true;
188
189 base::FilePath natives_data_path;
190 base::FilePath snapshot_data_path;
191 GetV8FilePaths(&natives_data_path, &snapshot_data_path);
192
193 base::File natives_file;
194 base::File snapshot_file; 171 base::File snapshot_file;
195 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ; 172 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
196 173
197 LoadV8SnapshotResult result; 174 LoadV8FileResult result;
198 if (!OpenV8File(natives_data_path, flags, natives_file) || 175 if (!OpenV8File(snapshot_data_path, flags, snapshot_file)) {
199 !OpenV8File(snapshot_data_path, flags, snapshot_file)) { 176 result = V8_LOAD_FAILED_OPEN;
200 result = LoadV8SnapshotResult::FAILED_OPEN; 177 } else if (!MapV8File(&g_mapped_snapshot, snapshot_file.Pass())) {
201 } else if (!MapV8Files(natives_file.Pass(), snapshot_file.Pass())) { 178 result = V8_LOAD_FAILED_MAP;
202 result = LoadV8SnapshotResult::FAILED_MAP;
203 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA) 179 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA)
204 } else if (!VerifyV8SnapshotFile(g_mapped_natives, g_natives_fingerprint) || 180 } else if (!VerifyV8StartupFile(g_mapped_snapshot, g_snapshot_fingerprint)) {
205 !VerifyV8SnapshotFile(g_mapped_snapshot, g_snapshot_fingerprint)) { 181 result = V8_LOAD_FAILED_VERIFY;
206 result = LoadV8SnapshotResult::FAILED_VERIFY;
207 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA 182 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA
208 } else { 183 } else {
209 result = LoadV8SnapshotResult::SUCCESS; 184 result = V8_LOAD_SUCCESS;
210 } 185 }
186 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.LoadV8Snapshot.Result", result,
187 V8_LOAD_MAX_VALUE);
188 }
211 189
212 UMA_HISTOGRAM_ENUMERATION("V8.Initializer.LoadV8Snapshot.Result", 190 void V8Initializer::LoadV8Natives() {
213 result, 191 if (g_mapped_natives)
214 LoadV8SnapshotResult::MAX_VALUE); 192 return;
215 return result == LoadV8SnapshotResult::SUCCESS; 193
194 base::FilePath natives_data_path;
195 GetV8FilePath(&natives_data_path, kNativesFileName);
196
197 base::File natives_file;
198 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
199
200 LoadV8FileResult result;
201 if (!OpenV8File(natives_data_path, flags, natives_file)) {
202 result = V8_LOAD_FAILED_OPEN;
203 } else if (!MapV8File(&g_mapped_natives, natives_file.Pass())) {
204 result = V8_LOAD_FAILED_MAP;
205 #if defined(V8_VERIFY_EXTERNAL_STARTUP_DATA)
206 } else if (!VerifyV8StartupFile(g_mapped_natives, g_natives_fingerprint)) {
207 result = V8_LOAD_FAILED_VERIFY;
208 #endif // V8_VERIFY_EXTERNAL_STARTUP_DATA
209 } else {
210 result = V8_LOAD_SUCCESS;
211 }
rmcilroy 2015/06/02 15:20:27 Could you share the code from lines 168-185 with 1
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
212 if (result != V8_LOAD_SUCCESS) {
213 LOG(FATAL) << "Couldn't mmap v8 natives data file";
214 }
216 } 215 }
217 216
218 // static 217 // static
219 bool V8Initializer::LoadV8SnapshotFromFD(base::PlatformFile natives_pf, 218 bool V8Initializer::LoadV8SnapshotFromFD(base::PlatformFile snapshot_pf,
220 int64 natives_offset,
221 int64 natives_size,
222 base::PlatformFile snapshot_pf,
223 int64 snapshot_offset, 219 int64 snapshot_offset,
224 int64 snapshot_size) { 220 int64 snapshot_size) {
225 if (g_mapped_natives && g_mapped_snapshot) 221 if (g_mapped_snapshot)
226 return true; 222 return true;
227 223
224 if (snapshot_pf == -1)
225 return false;
226
227 base::MemoryMappedFile::Region snapshot_region =
228 base::MemoryMappedFile::Region::kWholeFile;
229 if (snapshot_size != 0 || snapshot_offset != 0) {
230 snapshot_region =
231 base::MemoryMappedFile::Region(snapshot_offset, snapshot_size);
232 }
picksi 2015/06/02 16:02:15 nit: would the following be more readable : base:
Erik Corry Chromium.org 2015/06/04 11:40:40 Some compilers are very annoying about uninitializ
233
234 return MapV8File(&g_mapped_snapshot, base::File(snapshot_pf),
rmcilroy 2015/06/02 15:20:27 Please do the same UMA histograms here as in LoadV
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
235 snapshot_region);
236 }
237
238 void V8Initializer::LoadV8NativesFromFD(base::PlatformFile natives_pf,
239 int64 natives_offset,
240 int64 natives_size) {
241 if (g_mapped_natives)
242 return;
243
244 CHECK_NE(natives_pf, -1);
245
228 base::MemoryMappedFile::Region natives_region = 246 base::MemoryMappedFile::Region natives_region =
229 base::MemoryMappedFile::Region::kWholeFile; 247 base::MemoryMappedFile::Region::kWholeFile;
230 if (natives_size != 0 || natives_offset != 0) { 248 if (natives_size != 0 || natives_offset != 0) {
231 natives_region = 249 natives_region =
232 base::MemoryMappedFile::Region(natives_offset, natives_size); 250 base::MemoryMappedFile::Region(natives_offset, natives_size);
233 } 251 }
234 252
235 base::MemoryMappedFile::Region snapshot_region = 253 CHECK(MapV8File(&g_mapped_natives, base::File(natives_pf), natives_region));
rmcilroy 2015/06/02 15:20:27 LOG(FATAL) here like LoadV8Natives for consistency
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
236 base::MemoryMappedFile::Region::kWholeFile;
237 if (natives_size != 0 || natives_offset != 0) {
238 snapshot_region =
239 base::MemoryMappedFile::Region(snapshot_offset, snapshot_size);
240 }
241
242 return MapV8Files(base::File(natives_pf), base::File(snapshot_pf),
243 natives_region, snapshot_region);
244 } 254 }
245 255
246 // static 256 // static
247 bool V8Initializer::OpenV8FilesForChildProcesses( 257 bool V8Initializer::OpenV8FilesForChildProcesses(
248 base::PlatformFile* natives_fd_out, 258 base::PlatformFile* natives_fd_out,
249 base::PlatformFile* snapshot_fd_out) { 259 base::PlatformFile* snapshot_fd_out) {
250 base::FilePath natives_data_path; 260 base::FilePath natives_data_path;
251 base::FilePath snapshot_data_path; 261 base::FilePath snapshot_data_path;
252 GetV8FilePaths(&natives_data_path, &snapshot_data_path); 262 GetV8FilePath(&natives_data_path, kNativesFileName);
263 GetV8FilePath(&snapshot_data_path, kSnapshotFileName);
253 264
254 base::File natives_data_file; 265 base::File natives_data_file;
255 base::File snapshot_data_file; 266 base::File snapshot_data_file;
256 int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ; 267 int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
257 268
258 bool success = OpenV8File(natives_data_path, file_flags, natives_data_file) && 269 bool natives_success =
259 OpenV8File(snapshot_data_path, file_flags, snapshot_data_file); 270 OpenV8File(natives_data_path, file_flags, natives_data_file);
260 if (success) { 271 if (natives_success) {
261 *natives_fd_out = natives_data_file.TakePlatformFile(); 272 *natives_fd_out = natives_data_file.TakePlatformFile();
273 }
274 bool snapshot_success =
275 OpenV8File(snapshot_data_path, file_flags, snapshot_data_file);
276 if (snapshot_success) {
262 *snapshot_fd_out = snapshot_data_file.TakePlatformFile(); 277 *snapshot_fd_out = snapshot_data_file.TakePlatformFile();
263 } 278 }
264 return success; 279 // We can start up without the snapshot file, but not without the natives.
280 return natives_success;
265 } 281 }
266 282
267 #endif // V8_USE_EXTERNAL_STARTUP_DATA 283 #endif // V8_USE_EXTERNAL_STARTUP_DATA
268 284
269 // static 285 // static
270 void V8Initializer::Initialize(gin::IsolateHolder::ScriptMode mode) { 286 void V8Initializer::Initialize(gin::IsolateHolder::ScriptMode mode) {
271 static bool v8_is_initialized = false; 287 static bool v8_is_initialized = false;
272 if (v8_is_initialized) 288 if (v8_is_initialized)
273 return; 289 return;
274 290
275 v8::V8::InitializePlatform(V8Platform::Get()); 291 v8::V8::InitializePlatform(V8Platform::Get());
276 292
277 if (gin::IsolateHolder::kStrictMode == mode) { 293 if (gin::IsolateHolder::kStrictMode == mode) {
278 static const char use_strict[] = "--use_strict"; 294 static const char use_strict[] = "--use_strict";
279 v8::V8::SetFlagsFromString(use_strict, sizeof(use_strict) - 1); 295 v8::V8::SetFlagsFromString(use_strict, sizeof(use_strict) - 1);
280 } 296 }
281 297
282 #if defined(V8_USE_EXTERNAL_STARTUP_DATA) 298 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
283 v8::StartupData natives; 299 v8::StartupData natives;
284 natives.data = reinterpret_cast<const char*>(g_mapped_natives->data()); 300 natives.data = reinterpret_cast<const char*>(g_mapped_natives->data());
285 natives.raw_size = static_cast<int>(g_mapped_natives->length()); 301 natives.raw_size = static_cast<int>(g_mapped_natives->length());
286 v8::V8::SetNativesDataBlob(&natives); 302 v8::V8::SetNativesDataBlob(&natives);
287 303
288 v8::StartupData snapshot; 304 if (g_mapped_snapshot != NULL) {
289 snapshot.data = reinterpret_cast<const char*>(g_mapped_snapshot->data()); 305 v8::StartupData snapshot;
290 snapshot.raw_size = static_cast<int>(g_mapped_snapshot->length()); 306 snapshot.data = reinterpret_cast<const char*>(g_mapped_snapshot->data());
291 v8::V8::SetSnapshotDataBlob(&snapshot); 307 snapshot.raw_size = static_cast<int>(g_mapped_snapshot->length());
308 v8::V8::SetSnapshotDataBlob(&snapshot);
309 }
292 #endif // V8_USE_EXTERNAL_STARTUP_DATA 310 #endif // V8_USE_EXTERNAL_STARTUP_DATA
293 311
294 v8::V8::SetEntropySource(&GenerateEntropy); 312 v8::V8::SetEntropySource(&GenerateEntropy);
295 v8::V8::Initialize(); 313 v8::V8::Initialize();
296 314
297 v8_is_initialized = true; 315 v8_is_initialized = true;
298 } 316 }
299 317
300 // static 318 // static
301 void V8Initializer::GetV8ExternalSnapshotData(const char** natives_data_out, 319 void V8Initializer::GetV8ExternalSnapshotData(const char** natives_data_out,
302 int* natives_size_out, 320 int* natives_size_out,
303 const char** snapshot_data_out, 321 const char** snapshot_data_out,
304 int* snapshot_size_out) { 322 int* snapshot_size_out) {
305 if (!g_mapped_natives || !g_mapped_snapshot) { 323 if (!g_mapped_natives) {
picksi 2015/06/02 16:02:15 nit: remove the ! and swap if/else body?
Erik Corry Chromium.org 2015/06/04 11:40:40 Done.
306 *natives_data_out = *snapshot_data_out = NULL; 324 *natives_data_out = NULL;
307 *natives_size_out = *snapshot_size_out = 0; 325 *natives_size_out = 0;
308 return; 326 } else {
327 *natives_data_out = reinterpret_cast<const char*>(g_mapped_natives->data());
328 *natives_size_out = static_cast<int>(g_mapped_natives->length());
309 } 329 }
310 *natives_data_out = reinterpret_cast<const char*>(g_mapped_natives->data()); 330 if (!g_mapped_snapshot) {
311 *snapshot_data_out = reinterpret_cast<const char*>(g_mapped_snapshot->data()); 331 *snapshot_data_out = NULL;
312 *natives_size_out = static_cast<int>(g_mapped_natives->length()); 332 *snapshot_size_out = 0;
313 *snapshot_size_out = static_cast<int>(g_mapped_snapshot->length()); 333 } else {
334 *snapshot_data_out =
335 reinterpret_cast<const char*>(g_mapped_snapshot->data());
336 *snapshot_size_out = static_cast<int>(g_mapped_snapshot->length());
337 }
314 } 338 }
315 339
316 } // namespace gin 340 } // namespace gin
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698