/src/mozilla-central/dom/asmjscache/AsmJSCache.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "AsmJSCache.h" |
8 | | |
9 | | #include <stdio.h> |
10 | | |
11 | | #include "js/RootingAPI.h" |
12 | | #include "jsfriendapi.h" |
13 | | #include "mozilla/Assertions.h" |
14 | | #include "mozilla/CondVar.h" |
15 | | #include "mozilla/CycleCollectedJSRuntime.h" |
16 | | #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h" |
17 | | #include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h" |
18 | | #include "mozilla/dom/ContentChild.h" |
19 | | #include "mozilla/dom/PermissionMessageUtils.h" |
20 | | #include "mozilla/dom/quota/Client.h" |
21 | | #include "mozilla/dom/quota/QuotaManager.h" |
22 | | #include "mozilla/dom/quota/QuotaObject.h" |
23 | | #include "mozilla/dom/quota/UsageInfo.h" |
24 | | #include "mozilla/HashFunctions.h" |
25 | | #include "mozilla/ipc/BackgroundChild.h" |
26 | | #include "mozilla/ipc/BackgroundParent.h" |
27 | | #include "mozilla/ipc/BackgroundUtils.h" |
28 | | #include "mozilla/ipc/PBackgroundChild.h" |
29 | | #include "mozilla/Unused.h" |
30 | | #include "nsAutoPtr.h" |
31 | | #include "nsAtom.h" |
32 | | #include "nsIFile.h" |
33 | | #include "nsIPrincipal.h" |
34 | | #include "nsIRunnable.h" |
35 | | #include "nsISimpleEnumerator.h" |
36 | | #include "nsIThread.h" |
37 | | #include "nsJSPrincipals.h" |
38 | | #include "nsThreadUtils.h" |
39 | | #include "nsXULAppAPI.h" |
40 | | #include "prio.h" |
41 | | #include "private/pprio.h" |
42 | | #include "mozilla/Services.h" |
43 | | |
44 | | #define ASMJSCACHE_METADATA_FILE_NAME "metadata" |
45 | | #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module" |
46 | | |
47 | | using mozilla::dom::quota::AssertIsOnIOThread; |
48 | | using mozilla::dom::quota::DirectoryLock; |
49 | | using mozilla::dom::quota::PersistenceType; |
50 | | using mozilla::dom::quota::QuotaManager; |
51 | | using mozilla::dom::quota::QuotaObject; |
52 | | using mozilla::dom::quota::UsageInfo; |
53 | | using mozilla::ipc::AssertIsOnBackgroundThread; |
54 | | using mozilla::ipc::BackgroundChild; |
55 | | using mozilla::ipc::IsOnBackgroundThread; |
56 | | using mozilla::ipc::PBackgroundChild; |
57 | | using mozilla::ipc::PrincipalInfo; |
58 | | using mozilla::Unused; |
59 | | using mozilla::HashString; |
60 | | |
61 | | namespace mozilla { |
62 | | |
63 | | MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close); |
64 | | |
65 | | namespace dom { |
66 | | namespace asmjscache { |
67 | | |
68 | | namespace { |
69 | | |
70 | | class ParentRunnable; |
71 | | |
72 | | // Anything smaller should compile fast enough that caching will just add |
73 | | // overhead. |
74 | | static const size_t sMinCachedModuleLength = 10000; |
75 | | |
76 | | // The number of characters to hash into the Metadata::Entry::mFastHash. |
77 | | static const unsigned sNumFastHashChars = 4096; |
78 | | |
79 | | // Track all live parent actors. |
80 | | typedef nsTArray<const ParentRunnable*> ParentActorArray; |
81 | | StaticAutoPtr<ParentActorArray> sLiveParentActors; |
82 | | |
83 | | nsresult |
84 | | WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata) |
85 | 0 | { |
86 | 0 | int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE; |
87 | 0 |
|
88 | 0 | JS::BuildIdCharVector buildId; |
89 | 0 | bool ok = GetBuildId(&buildId); |
90 | 0 | NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); |
91 | 0 |
|
92 | 0 | ScopedPRFileDesc fd; |
93 | 0 | nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget()); |
94 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
95 | 0 |
|
96 | 0 | uint32_t length = buildId.length(); |
97 | 0 | int32_t bytesWritten = PR_Write(fd, &length, sizeof(length)); |
98 | 0 | NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED); |
99 | 0 |
|
100 | 0 | bytesWritten = PR_Write(fd, buildId.begin(), length); |
101 | 0 | NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED); |
102 | 0 |
|
103 | 0 | bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata)); |
104 | 0 | NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED); |
105 | 0 |
|
106 | 0 | return NS_OK; |
107 | 0 | } |
108 | | |
109 | | nsresult |
110 | | ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata) |
111 | 0 | { |
112 | 0 | int32_t openFlags = PR_RDONLY; |
113 | 0 |
|
114 | 0 | ScopedPRFileDesc fd; |
115 | 0 | nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget()); |
116 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
117 | 0 |
|
118 | 0 | // Read the buildid and check that it matches the current buildid |
119 | 0 |
|
120 | 0 | JS::BuildIdCharVector currentBuildId; |
121 | 0 | bool ok = GetBuildId(¤tBuildId); |
122 | 0 | NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); |
123 | 0 |
|
124 | 0 | uint32_t length; |
125 | 0 | int32_t bytesRead = PR_Read(fd, &length, sizeof(length)); |
126 | 0 | NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED); |
127 | 0 |
|
128 | 0 | NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED); |
129 | 0 |
|
130 | 0 | JS::BuildIdCharVector fileBuildId; |
131 | 0 | ok = fileBuildId.resize(length); |
132 | 0 | NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); |
133 | 0 |
|
134 | 0 | bytesRead = PR_Read(fd, fileBuildId.begin(), length); |
135 | 0 | NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED); |
136 | 0 |
|
137 | 0 | for (uint32_t i = 0; i < length; i++) { |
138 | 0 | if (currentBuildId[i] != fileBuildId[i]) { |
139 | 0 | return NS_ERROR_FAILURE; |
140 | 0 | } |
141 | 0 | } |
142 | 0 |
|
143 | 0 | // Read the Metadata struct |
144 | 0 |
|
145 | 0 | bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata)); |
146 | 0 | NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED); |
147 | 0 |
|
148 | 0 | return NS_OK; |
149 | 0 | } |
150 | | |
151 | | nsresult |
152 | | GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex, nsIFile** aCacheFile) |
153 | 0 | { |
154 | 0 | nsCOMPtr<nsIFile> cacheFile; |
155 | 0 | nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile)); |
156 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
157 | 0 |
|
158 | 0 | nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE); |
159 | 0 | cacheFileName.AppendInt(aModuleIndex); |
160 | 0 | rv = cacheFile->Append(cacheFileName); |
161 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
162 | 0 |
|
163 | 0 | cacheFile.forget(aCacheFile); |
164 | 0 | return NS_OK; |
165 | 0 | } |
166 | | |
167 | | class AutoDecreaseUsageForOrigin |
168 | | { |
169 | | const nsACString& mGroup; |
170 | | const nsACString& mOrigin; |
171 | | |
172 | | public: |
173 | | uint64_t mFreed; |
174 | | |
175 | | AutoDecreaseUsageForOrigin(const nsACString& aGroup, |
176 | | const nsACString& aOrigin) |
177 | | |
178 | | : mGroup(aGroup), |
179 | | mOrigin(aOrigin), |
180 | | mFreed(0) |
181 | 0 | { } |
182 | | |
183 | | ~AutoDecreaseUsageForOrigin() |
184 | 0 | { |
185 | 0 | AssertIsOnIOThread(); |
186 | 0 |
|
187 | 0 | if (!mFreed) { |
188 | 0 | return; |
189 | 0 | } |
190 | 0 | |
191 | 0 | QuotaManager* qm = QuotaManager::Get(); |
192 | 0 | MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); |
193 | 0 |
|
194 | 0 | qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY, |
195 | 0 | mGroup, mOrigin, mFreed); |
196 | 0 | } |
197 | | }; |
198 | | |
199 | | static void |
200 | | EvictEntries(nsIFile* aDirectory, const nsACString& aGroup, |
201 | | const nsACString& aOrigin, uint64_t aNumBytes, |
202 | | Metadata& aMetadata) |
203 | 0 | { |
204 | 0 | AssertIsOnIOThread(); |
205 | 0 |
|
206 | 0 | AutoDecreaseUsageForOrigin usage(aGroup, aOrigin); |
207 | 0 |
|
208 | 0 | for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) { |
209 | 0 | Metadata::Entry& entry = aMetadata.mEntries[i]; |
210 | 0 | unsigned moduleIndex = entry.mModuleIndex; |
211 | 0 |
|
212 | 0 | nsCOMPtr<nsIFile> file; |
213 | 0 | nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file)); |
214 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
215 | 0 | return; |
216 | 0 | } |
217 | 0 | |
218 | 0 | bool exists; |
219 | 0 | rv = file->Exists(&exists); |
220 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
221 | 0 | return; |
222 | 0 | } |
223 | 0 | |
224 | 0 | if (exists) { |
225 | 0 | int64_t fileSize; |
226 | 0 | rv = file->GetFileSize(&fileSize); |
227 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
228 | 0 | return; |
229 | 0 | } |
230 | 0 | |
231 | 0 | rv = file->Remove(false); |
232 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
233 | 0 | return; |
234 | 0 | } |
235 | 0 | |
236 | 0 | usage.mFreed += fileSize; |
237 | 0 | } |
238 | 0 |
|
239 | 0 | entry.clear(); |
240 | 0 | } |
241 | 0 | } |
242 | | |
243 | | /******************************************************************************* |
244 | | * Client |
245 | | ******************************************************************************/ |
246 | | |
247 | | class Client |
248 | | : public quota::Client |
249 | | { |
250 | | static Client* sInstance; |
251 | | |
252 | | bool mShutdownRequested; |
253 | | |
254 | | public: |
255 | | Client(); |
256 | | |
257 | | static bool |
258 | | IsShuttingDownOnBackgroundThread() |
259 | 0 | { |
260 | 0 | AssertIsOnBackgroundThread(); |
261 | 0 |
|
262 | 0 | if (sInstance) { |
263 | 0 | return sInstance->IsShuttingDown(); |
264 | 0 | } |
265 | 0 | |
266 | 0 | return QuotaManager::IsShuttingDown(); |
267 | 0 | } |
268 | | |
269 | | static bool |
270 | | IsShuttingDownOnNonBackgroundThread() |
271 | 0 | { |
272 | 0 | MOZ_ASSERT(!IsOnBackgroundThread()); |
273 | 0 |
|
274 | 0 | return QuotaManager::IsShuttingDown(); |
275 | 0 | } |
276 | | |
277 | | bool |
278 | | IsShuttingDown() const |
279 | 0 | { |
280 | 0 | AssertIsOnBackgroundThread(); |
281 | 0 |
|
282 | 0 | return mShutdownRequested; |
283 | 0 | } |
284 | | |
285 | | NS_INLINE_DECL_REFCOUNTING(Client, override) |
286 | | |
287 | | Type |
288 | | GetType() override; |
289 | | |
290 | | nsresult |
291 | | InitOrigin(PersistenceType aPersistenceType, |
292 | | const nsACString& aGroup, |
293 | | const nsACString& aOrigin, |
294 | | const AtomicBool& aCanceled, |
295 | | UsageInfo* aUsageInfo) override; |
296 | | |
297 | | nsresult |
298 | | GetUsageForOrigin(PersistenceType aPersistenceType, |
299 | | const nsACString& aGroup, |
300 | | const nsACString& aOrigin, |
301 | | const AtomicBool& aCanceled, |
302 | | UsageInfo* aUsageInfo) override; |
303 | | |
304 | | void |
305 | | OnOriginClearCompleted(PersistenceType aPersistenceType, |
306 | | const nsACString& aOrigin) |
307 | | override; |
308 | | |
309 | | void |
310 | | ReleaseIOThreadObjects() override; |
311 | | |
312 | | void |
313 | | AbortOperations(const nsACString& aOrigin) override; |
314 | | |
315 | | void |
316 | | AbortOperationsForProcess(ContentParentId aContentParentId) override; |
317 | | |
318 | | void |
319 | | StartIdleMaintenance() override; |
320 | | |
321 | | void |
322 | | StopIdleMaintenance() override; |
323 | | |
324 | | void |
325 | | ShutdownWorkThreads() override; |
326 | | |
327 | | private: |
328 | | ~Client() override; |
329 | | }; |
330 | | |
331 | | // FileDescriptorHolder owns a file descriptor and its memory mapping. |
332 | | // FileDescriptorHolder is derived by two runnable classes (that is, |
333 | | // (Parent|Child)Runnable. |
334 | | class FileDescriptorHolder : public Runnable |
335 | | { |
336 | | public: |
337 | | FileDescriptorHolder() |
338 | | : Runnable("dom::asmjscache::FileDescriptorHolder") |
339 | | , mQuotaObject(nullptr) |
340 | | , mFileSize(INT64_MIN) |
341 | | , mFileDesc(nullptr) |
342 | | , mFileMap(nullptr) |
343 | | , mMappedMemory(nullptr) |
344 | 0 | { } |
345 | | |
346 | | ~FileDescriptorHolder() override |
347 | 0 | { |
348 | 0 | // These resources should have already been released by Finish(). |
349 | 0 | MOZ_ASSERT(!mQuotaObject); |
350 | 0 | MOZ_ASSERT(!mMappedMemory); |
351 | 0 | MOZ_ASSERT(!mFileMap); |
352 | 0 | MOZ_ASSERT(!mFileDesc); |
353 | 0 | } |
354 | | |
355 | | size_t |
356 | | FileSize() const |
357 | 0 | { |
358 | 0 | MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file"); |
359 | 0 | return mFileSize; |
360 | 0 | } |
361 | | |
362 | | PRFileDesc* |
363 | | FileDesc() const |
364 | 0 | { |
365 | 0 | MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file"); |
366 | 0 | return mFileDesc; |
367 | 0 | } |
368 | | |
369 | | bool |
370 | | MapMemory(OpenMode aOpenMode) |
371 | 0 | { |
372 | 0 | MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice"); |
373 | 0 |
|
374 | 0 | PRFileMapProtect mapFlags = aOpenMode == eOpenForRead ? PR_PROT_READONLY |
375 | 0 | : PR_PROT_READWRITE; |
376 | 0 |
|
377 | 0 | mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags); |
378 | 0 | NS_ENSURE_TRUE(mFileMap, false); |
379 | 0 |
|
380 | 0 | mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize); |
381 | 0 | NS_ENSURE_TRUE(mMappedMemory, false); |
382 | 0 |
|
383 | 0 | return true; |
384 | 0 | } |
385 | | |
386 | | void* |
387 | | MappedMemory() const |
388 | 0 | { |
389 | 0 | MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file"); |
390 | 0 | return mMappedMemory; |
391 | 0 | } |
392 | | |
393 | | protected: |
394 | | // This method must be called before the directory lock is released (the lock |
395 | | // is protecting these resources). It is idempotent, so it is ok to call |
396 | | // multiple times (or before the file has been fully opened). |
397 | | void |
398 | | Finish() |
399 | 0 | { |
400 | 0 | if (mMappedMemory) { |
401 | 0 | PR_MemUnmap(mMappedMemory, mFileSize); |
402 | 0 | mMappedMemory = nullptr; |
403 | 0 | } |
404 | 0 | if (mFileMap) { |
405 | 0 | PR_CloseFileMap(mFileMap); |
406 | 0 | mFileMap = nullptr; |
407 | 0 | } |
408 | 0 | if (mFileDesc) { |
409 | 0 | PR_Close(mFileDesc); |
410 | 0 | mFileDesc = nullptr; |
411 | 0 | } |
412 | 0 |
|
413 | 0 | // Holding the QuotaObject alive until all the cache files are closed enables |
414 | 0 | // assertions in QuotaManager that the cache entry isn't cleared while we |
415 | 0 | // are working on it. |
416 | 0 | mQuotaObject = nullptr; |
417 | 0 | } |
418 | | |
419 | | RefPtr<QuotaObject> mQuotaObject; |
420 | | int64_t mFileSize; |
421 | | PRFileDesc* mFileDesc; |
422 | | PRFileMap* mFileMap; |
423 | | void* mMappedMemory; |
424 | | }; |
425 | | |
426 | | // A runnable that implements a state machine required to open a cache entry. |
427 | | // It executes in the parent for a cache access originating in the child. |
428 | | // This runnable gets registered as an IPDL subprotocol actor so that it |
429 | | // can communicate with the corresponding ChildRunnable. |
430 | | class ParentRunnable final |
431 | | : public FileDescriptorHolder |
432 | | , public quota::OpenDirectoryListener |
433 | | , public PAsmJSCacheEntryParent |
434 | | { |
435 | | public: |
436 | | // We need to always declare refcounting because |
437 | | // OpenDirectoryListener has pure-virtual refcounting. |
438 | | NS_DECL_ISUPPORTS_INHERITED |
439 | | NS_DECL_NSIRUNNABLE |
440 | | |
441 | | ParentRunnable(const PrincipalInfo& aPrincipalInfo, |
442 | | OpenMode aOpenMode, |
443 | | const WriteParams& aWriteParams) |
444 | | : mOwningEventTarget(GetCurrentThreadEventTarget()), |
445 | | mPrincipalInfo(aPrincipalInfo), |
446 | | mOpenMode(aOpenMode), |
447 | | mWriteParams(aWriteParams), |
448 | | mOperationMayProceed(true), |
449 | | mModuleIndex(0), |
450 | | mState(eInitial), |
451 | | mResult(JS::AsmJSCache_InternalError), |
452 | | mActorDestroyed(false), |
453 | | mOpened(false) |
454 | 0 | { |
455 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
456 | 0 | AssertIsOnOwningThread(); |
457 | 0 | } |
458 | | |
459 | | private: |
460 | | ~ParentRunnable() override |
461 | 0 | { |
462 | 0 | MOZ_ASSERT(mState == eFinished); |
463 | 0 | MOZ_ASSERT(!mDirectoryLock); |
464 | 0 | MOZ_ASSERT(mActorDestroyed); |
465 | 0 | } |
466 | | |
467 | | #ifdef DEBUG |
468 | | bool |
469 | | IsOnOwningThread() const |
470 | | { |
471 | | MOZ_ASSERT(mOwningEventTarget); |
472 | | |
473 | | bool current; |
474 | | return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) && current; |
475 | | } |
476 | | #endif |
477 | | |
478 | | void |
479 | | AssertIsOnOwningThread() const |
480 | 0 | { |
481 | 0 | MOZ_ASSERT(IsOnBackgroundThread()); |
482 | 0 | MOZ_ASSERT(IsOnOwningThread()); |
483 | 0 | } |
484 | | |
485 | | void |
486 | | AssertIsOnNonOwningThread() const |
487 | 0 | { |
488 | 0 | MOZ_ASSERT(!IsOnBackgroundThread()); |
489 | 0 | MOZ_ASSERT(!IsOnOwningThread()); |
490 | 0 | } |
491 | | |
492 | | bool |
493 | | IsActorDestroyed() const |
494 | 0 | { |
495 | 0 | AssertIsOnOwningThread(); |
496 | 0 |
|
497 | 0 | return mActorDestroyed; |
498 | 0 | } |
499 | | |
500 | | // May be called on any thread, but you should call IsActorDestroyed() if |
501 | | // you know you're on the background thread because it is slightly faster. |
502 | | bool |
503 | | OperationMayProceed() const |
504 | 0 | { |
505 | 0 | return mOperationMayProceed; |
506 | 0 | } |
507 | | |
508 | | // This method is called on the owning thread when the JS engine is finished |
509 | | // reading/writing the cache entry. |
510 | | void |
511 | | Close() |
512 | 0 | { |
513 | 0 | AssertIsOnOwningThread(); |
514 | 0 | MOZ_ASSERT(mState == eOpened); |
515 | 0 | MOZ_ASSERT(mResult == JS::AsmJSCache_Success); |
516 | 0 |
|
517 | 0 | mState = eFinished; |
518 | 0 |
|
519 | 0 | MOZ_ASSERT(mOpened); |
520 | 0 | mOpened = false; |
521 | 0 |
|
522 | 0 | FinishOnOwningThread(); |
523 | 0 |
|
524 | 0 | if (!mActorDestroyed) { |
525 | 0 | Unused << Send__delete__(this, mResult); |
526 | 0 | } |
527 | 0 | } |
528 | | |
529 | | // This method is called upon any failure that prevents the eventual opening |
530 | | // of the cache entry. |
531 | | void |
532 | | Fail() |
533 | 0 | { |
534 | 0 | AssertIsOnOwningThread(); |
535 | 0 | MOZ_ASSERT(mState != eFinished); |
536 | 0 | MOZ_ASSERT(mResult != JS::AsmJSCache_Success); |
537 | 0 |
|
538 | 0 | mState = eFinished; |
539 | 0 |
|
540 | 0 | MOZ_ASSERT(!mOpened); |
541 | 0 |
|
542 | 0 | FinishOnOwningThread(); |
543 | 0 |
|
544 | 0 | if (!mActorDestroyed) { |
545 | 0 | Unused << Send__delete__(this, mResult); |
546 | 0 | } |
547 | 0 | } |
548 | | |
549 | | // The same as method above but is intended to be called off the owning |
550 | | // thread. |
551 | | void |
552 | | FailOnNonOwningThread() |
553 | 0 | { |
554 | 0 | AssertIsOnNonOwningThread(); |
555 | 0 | MOZ_ASSERT(mState != eOpened && |
556 | 0 | mState != eFailing && |
557 | 0 | mState != eFinished); |
558 | 0 |
|
559 | 0 | mState = eFailing; |
560 | 0 | MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); |
561 | 0 | } |
562 | | |
563 | | nsresult |
564 | | InitOnMainThread(); |
565 | | |
566 | | void |
567 | | OpenDirectory(); |
568 | | |
569 | | nsresult |
570 | | ReadMetadata(); |
571 | | |
572 | | nsresult |
573 | | OpenCacheFileForWrite(); |
574 | | |
575 | | nsresult |
576 | | OpenCacheFileForRead(); |
577 | | |
578 | | void |
579 | | FinishOnOwningThread(); |
580 | | |
581 | | void |
582 | | DispatchToIOThread() |
583 | 0 | { |
584 | 0 | AssertIsOnOwningThread(); |
585 | 0 |
|
586 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) || |
587 | 0 | IsActorDestroyed()) { |
588 | 0 | Fail(); |
589 | 0 | return; |
590 | 0 | } |
591 | 0 | |
592 | 0 | QuotaManager* qm = QuotaManager::Get(); |
593 | 0 | MOZ_ASSERT(qm); |
594 | 0 |
|
595 | 0 | nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); |
596 | 0 | if (NS_FAILED(rv)) { |
597 | 0 | Fail(); |
598 | 0 | return; |
599 | 0 | } |
600 | 0 | } |
601 | | |
602 | | // OpenDirectoryListener overrides. |
603 | | void |
604 | | DirectoryLockAcquired(DirectoryLock* aLock) override; |
605 | | |
606 | | void |
607 | | DirectoryLockFailed() override; |
608 | | |
609 | | // IPDL methods. |
610 | | void |
611 | | ActorDestroy(ActorDestroyReason why) override |
612 | 0 | { |
613 | 0 | AssertIsOnOwningThread(); |
614 | 0 | MOZ_ASSERT(!mActorDestroyed); |
615 | 0 | MOZ_ASSERT(mOperationMayProceed); |
616 | 0 |
|
617 | 0 | mActorDestroyed = true; |
618 | 0 | mOperationMayProceed = false; |
619 | 0 |
|
620 | 0 | // Assume ActorDestroy can happen at any time, so we can't probe the |
621 | 0 | // current state since mState can be modified on any thread (only one |
622 | 0 | // thread at a time based on the state machine). |
623 | 0 | // However we can use mOpened which is only touched on the owning thread. |
624 | 0 | // If mOpened is true, we can also modify mState since we are guaranteed |
625 | 0 | // that there are no pending runnables which would probe mState to decide |
626 | 0 | // what code needs to run (there shouldn't be any running runnables on |
627 | 0 | // other threads either). |
628 | 0 |
|
629 | 0 | if (mOpened) { |
630 | 0 | Close(); |
631 | 0 |
|
632 | 0 | MOZ_ASSERT(mState == eFinished); |
633 | 0 | } |
634 | 0 |
|
635 | 0 | // We don't have to call Fail() if mOpened is not true since it means that |
636 | 0 | // either nothing has been initialized yet, so nothing to cleanup or there |
637 | 0 | // are pending runnables that will detect that the actor has been destroyed |
638 | 0 | // and call Fail(). |
639 | 0 | } |
640 | | |
641 | | mozilla::ipc::IPCResult |
642 | | RecvSelectCacheFileToRead(const OpenMetadataForReadResponse& aResponse) |
643 | | override |
644 | 0 | { |
645 | 0 | AssertIsOnOwningThread(); |
646 | 0 | MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead); |
647 | 0 | MOZ_ASSERT(mOpenMode == eOpenForRead); |
648 | 0 | MOZ_ASSERT(!mOpened); |
649 | 0 |
|
650 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) { |
651 | 0 | Fail(); |
652 | 0 | return IPC_OK(); |
653 | 0 | } |
654 | 0 |
|
655 | 0 | switch (aResponse.type()) { |
656 | 0 | case OpenMetadataForReadResponse::TAsmJSCacheResult: { |
657 | 0 | MOZ_ASSERT(aResponse.get_AsmJSCacheResult() != JS::AsmJSCache_Success); |
658 | 0 |
|
659 | 0 | mResult = aResponse.get_AsmJSCacheResult(); |
660 | 0 |
|
661 | 0 | // This ParentRunnable can only be held alive by the IPDL. Fail() |
662 | 0 | // clears that last reference. So we need to add a self reference here. |
663 | 0 | RefPtr<ParentRunnable> kungFuDeathGrip = this; |
664 | 0 |
|
665 | 0 | Fail(); |
666 | 0 |
|
667 | 0 | break; |
668 | 0 | } |
669 | 0 |
|
670 | 0 | case OpenMetadataForReadResponse::Tuint32_t: |
671 | 0 | // A cache entry has been selected to open. |
672 | 0 | mModuleIndex = aResponse.get_uint32_t(); |
673 | 0 |
|
674 | 0 | mState = eReadyToOpenCacheFileForRead; |
675 | 0 |
|
676 | 0 | DispatchToIOThread(); |
677 | 0 |
|
678 | 0 | break; |
679 | 0 |
|
680 | 0 | default: |
681 | 0 | MOZ_CRASH("Should never get here!"); |
682 | 0 | } |
683 | 0 |
|
684 | 0 | return IPC_OK(); |
685 | 0 | } |
686 | | |
687 | | mozilla::ipc::IPCResult |
688 | | RecvClose() override |
689 | 0 | { |
690 | 0 | AssertIsOnOwningThread(); |
691 | 0 | MOZ_ASSERT(mState == eOpened); |
692 | 0 |
|
693 | 0 | // This ParentRunnable can only be held alive by the IPDL. Close() clears |
694 | 0 | // that last reference. So we need to add a self reference here. |
695 | 0 | RefPtr<ParentRunnable> kungFuDeathGrip = this; |
696 | 0 |
|
697 | 0 | Close(); |
698 | 0 |
|
699 | 0 | MOZ_ASSERT(mState == eFinished); |
700 | 0 |
|
701 | 0 | return IPC_OK(); |
702 | 0 | } |
703 | | |
704 | | nsCOMPtr<nsIEventTarget> mOwningEventTarget; |
705 | | const PrincipalInfo mPrincipalInfo; |
706 | | const OpenMode mOpenMode; |
707 | | const WriteParams mWriteParams; |
708 | | |
709 | | // State initialized during eInitial: |
710 | | nsCString mSuffix; |
711 | | nsCString mGroup; |
712 | | nsCString mOrigin; |
713 | | RefPtr<DirectoryLock> mDirectoryLock; |
714 | | |
715 | | // State initialized during eReadyToReadMetadata |
716 | | nsCOMPtr<nsIFile> mDirectory; |
717 | | nsCOMPtr<nsIFile> mMetadataFile; |
718 | | Metadata mMetadata; |
719 | | |
720 | | Atomic<bool> mOperationMayProceed; |
721 | | |
722 | | // State initialized during eWaitingToOpenCacheFileForRead |
723 | | unsigned mModuleIndex; |
724 | | |
725 | | enum State { |
726 | | eInitial, // Just created, waiting to be dispatched to main thread |
727 | | eWaitingToFinishInit, // Waiting to finish initialization |
728 | | eWaitingToOpenDirectory, // Waiting to open directory |
729 | | eWaitingToOpenMetadata, // Waiting to be called back from OpenDirectory |
730 | | eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread |
731 | | eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead |
732 | | eWaitingToOpenCacheFileForRead, // Waiting to hear back from child |
733 | | eReadyToOpenCacheFileForRead, // Waiting to open cache file for read |
734 | | eSendingCacheFile, // Waiting to send OnOpenCacheFile on the owning thread |
735 | | eOpened, // Finished calling OnOpenCacheFile, waiting to be closed |
736 | | eFailing, // Just failed, waiting to be dispatched to the owning thread |
737 | | eFinished, // Terminal state |
738 | | }; |
739 | | State mState; |
740 | | JS::AsmJSCacheResult mResult; |
741 | | |
742 | | bool mActorDestroyed; |
743 | | bool mOpened; |
744 | | }; |
745 | | |
746 | | nsresult |
747 | | ParentRunnable::InitOnMainThread() |
748 | 0 | { |
749 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
750 | 0 | MOZ_ASSERT(mState == eInitial); |
751 | 0 | MOZ_ASSERT(mPrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo); |
752 | 0 |
|
753 | 0 | nsresult rv; |
754 | 0 | nsCOMPtr<nsIPrincipal> principal = |
755 | 0 | PrincipalInfoToPrincipal(mPrincipalInfo, &rv); |
756 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
757 | 0 | return rv; |
758 | 0 | } |
759 | 0 | |
760 | 0 | rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, |
761 | 0 | &mOrigin); |
762 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
763 | 0 |
|
764 | 0 | return NS_OK; |
765 | 0 | } |
766 | | |
767 | | void |
768 | | ParentRunnable::OpenDirectory() |
769 | 0 | { |
770 | 0 | AssertIsOnOwningThread(); |
771 | 0 | MOZ_ASSERT(mState == eWaitingToFinishInit || |
772 | 0 | mState == eWaitingToOpenDirectory); |
773 | 0 | MOZ_ASSERT(QuotaManager::Get()); |
774 | 0 |
|
775 | 0 | mState = eWaitingToOpenMetadata; |
776 | 0 |
|
777 | 0 | // XXX The exclusive lock shouldn't be needed for read operations. |
778 | 0 | QuotaManager::Get()->OpenDirectory(quota::PERSISTENCE_TYPE_TEMPORARY, |
779 | 0 | mGroup, |
780 | 0 | mOrigin, |
781 | 0 | quota::Client::ASMJS, |
782 | 0 | /* aExclusive */ true, |
783 | 0 | this); |
784 | 0 | } |
785 | | |
786 | | nsresult |
787 | | ParentRunnable::ReadMetadata() |
788 | 0 | { |
789 | 0 | AssertIsOnIOThread(); |
790 | 0 | MOZ_ASSERT(mState == eReadyToReadMetadata); |
791 | 0 |
|
792 | 0 | QuotaManager* qm = QuotaManager::Get(); |
793 | 0 | MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); |
794 | 0 |
|
795 | 0 | nsresult rv = |
796 | 0 | qm->EnsureOriginIsInitialized(quota::PERSISTENCE_TYPE_TEMPORARY, mSuffix, |
797 | 0 | mGroup, mOrigin, getter_AddRefs(mDirectory)); |
798 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
799 | 0 | mResult = JS::AsmJSCache_StorageInitFailure; |
800 | 0 | return rv; |
801 | 0 | } |
802 | 0 | |
803 | 0 | rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); |
804 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
805 | 0 |
|
806 | 0 | bool exists; |
807 | 0 | rv = mDirectory->Exists(&exists); |
808 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
809 | 0 |
|
810 | 0 | if (!exists) { |
811 | 0 | rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); |
812 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
813 | 0 | } else { |
814 | 0 | DebugOnly<bool> isDirectory; |
815 | 0 | MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory))); |
816 | 0 | MOZ_ASSERT(isDirectory, "Should have caught this earlier!"); |
817 | 0 | } |
818 | 0 |
|
819 | 0 | rv = mDirectory->Clone(getter_AddRefs(mMetadataFile)); |
820 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
821 | 0 |
|
822 | 0 | rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME)); |
823 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
824 | 0 |
|
825 | 0 | rv = mMetadataFile->Exists(&exists); |
826 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
827 | 0 |
|
828 | 0 | if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) { |
829 | 0 | exists = false; |
830 | 0 | } |
831 | 0 |
|
832 | 0 | if (!exists) { |
833 | 0 | // If we are reading, we can't possibly have a cache hit. |
834 | 0 | if (mOpenMode == eOpenForRead) { |
835 | 0 | return NS_ERROR_FILE_NOT_FOUND; |
836 | 0 | } |
837 | 0 | |
838 | 0 | // Initialize Metadata with a valid empty state for the LRU cache. |
839 | 0 | for (unsigned i = 0; i < Metadata::kNumEntries; i++) { |
840 | 0 | Metadata::Entry& entry = mMetadata.mEntries[i]; |
841 | 0 | entry.mModuleIndex = i; |
842 | 0 | entry.clear(); |
843 | 0 | } |
844 | 0 | } |
845 | 0 |
|
846 | 0 | return NS_OK; |
847 | 0 | } |
848 | | |
849 | | nsresult |
850 | | ParentRunnable::OpenCacheFileForWrite() |
851 | 0 | { |
852 | 0 | AssertIsOnIOThread(); |
853 | 0 | MOZ_ASSERT(mState == eReadyToReadMetadata); |
854 | 0 | MOZ_ASSERT(mOpenMode == eOpenForWrite); |
855 | 0 |
|
856 | 0 | mFileSize = mWriteParams.mSize; |
857 | 0 |
|
858 | 0 | // Kick out the oldest entry in the LRU queue in the metadata. |
859 | 0 | mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex; |
860 | 0 |
|
861 | 0 | nsCOMPtr<nsIFile> file; |
862 | 0 | nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file)); |
863 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
864 | 0 |
|
865 | 0 | QuotaManager* qm = QuotaManager::Get(); |
866 | 0 | MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); |
867 | 0 |
|
868 | 0 | // Create the QuotaObject before all file IO and keep it alive until caching |
869 | 0 | // completes to get maximum assertion coverage in QuotaManager against |
870 | 0 | // concurrent removal, etc. |
871 | 0 | mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup, |
872 | 0 | mOrigin, file); |
873 | 0 | NS_ENSURE_STATE(mQuotaObject); |
874 | 0 |
|
875 | 0 | if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize, |
876 | 0 | /* aTruncate */ false)) { |
877 | 0 | // If the request fails, it might be because mOrigin is using too much |
878 | 0 | // space (MaybeUpdateSize will not evict our own origin since it is |
879 | 0 | // active). Try to make some space by evicting LRU entries until there is |
880 | 0 | // enough space. |
881 | 0 | EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata); |
882 | 0 | if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize, |
883 | 0 | /* aTruncate */ false)) { |
884 | 0 | mResult = JS::AsmJSCache_QuotaExceeded; |
885 | 0 | return NS_ERROR_FAILURE; |
886 | 0 | } |
887 | 0 | } |
888 | 0 | |
889 | 0 | int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE; |
890 | 0 | rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc); |
891 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
892 | 0 |
|
893 | 0 | // Move the mModuleIndex's LRU entry to the recent end of the queue. |
894 | 0 | PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry); |
895 | 0 | Metadata::Entry& entry = mMetadata.mEntries[0]; |
896 | 0 | entry.mFastHash = mWriteParams.mFastHash; |
897 | 0 | entry.mNumChars = mWriteParams.mNumChars; |
898 | 0 | entry.mFullHash = mWriteParams.mFullHash; |
899 | 0 | entry.mModuleIndex = mModuleIndex; |
900 | 0 |
|
901 | 0 | rv = WriteMetadataFile(mMetadataFile, mMetadata); |
902 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
903 | 0 |
|
904 | 0 | return NS_OK; |
905 | 0 | } |
906 | | |
907 | | nsresult |
908 | | ParentRunnable::OpenCacheFileForRead() |
909 | 0 | { |
910 | 0 | AssertIsOnIOThread(); |
911 | 0 | MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead); |
912 | 0 | MOZ_ASSERT(mOpenMode == eOpenForRead); |
913 | 0 |
|
914 | 0 | nsCOMPtr<nsIFile> file; |
915 | 0 | nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file)); |
916 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
917 | 0 |
|
918 | 0 | QuotaManager* qm = QuotaManager::Get(); |
919 | 0 | MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); |
920 | 0 |
|
921 | 0 | // Even though it's not strictly necessary, create the QuotaObject before all |
922 | 0 | // file IO and keep it alive until caching completes to get maximum assertion |
923 | 0 | // coverage in QuotaManager against concurrent removal, etc. |
924 | 0 | mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup, |
925 | 0 | mOrigin, file); |
926 | 0 | NS_ENSURE_STATE(mQuotaObject); |
927 | 0 |
|
928 | 0 | rv = file->GetFileSize(&mFileSize); |
929 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
930 | 0 |
|
931 | 0 | int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD; |
932 | 0 | rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc); |
933 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
934 | 0 |
|
935 | 0 | // Move the mModuleIndex's LRU entry to the recent end of the queue. |
936 | 0 | unsigned lruIndex = 0; |
937 | 0 | while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) { |
938 | 0 | if (++lruIndex == Metadata::kNumEntries) { |
939 | 0 | return NS_ERROR_UNEXPECTED; |
940 | 0 | } |
941 | 0 | } |
942 | 0 | Metadata::Entry entry = mMetadata.mEntries[lruIndex]; |
943 | 0 | PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex); |
944 | 0 | mMetadata.mEntries[0] = entry; |
945 | 0 |
|
946 | 0 | rv = WriteMetadataFile(mMetadataFile, mMetadata); |
947 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
948 | 0 |
|
949 | 0 | return NS_OK; |
950 | 0 | } |
951 | | |
952 | | void |
953 | | ParentRunnable::FinishOnOwningThread() |
954 | 0 | { |
955 | 0 | AssertIsOnOwningThread(); |
956 | 0 |
|
957 | 0 | // Per FileDescriptorHolder::Finish()'s comment, call before |
958 | 0 | // releasing the directory lock. |
959 | 0 | FileDescriptorHolder::Finish(); |
960 | 0 |
|
961 | 0 | mDirectoryLock = nullptr; |
962 | 0 |
|
963 | 0 | MOZ_ASSERT(sLiveParentActors); |
964 | 0 | sLiveParentActors->RemoveElement(this); |
965 | 0 |
|
966 | 0 | if (sLiveParentActors->IsEmpty()) { |
967 | 0 | sLiveParentActors = nullptr; |
968 | 0 | } |
969 | 0 | } |
970 | | |
971 | | NS_IMETHODIMP |
972 | | ParentRunnable::Run() |
973 | 0 | { |
974 | 0 | nsresult rv; |
975 | 0 |
|
976 | 0 | // All success/failure paths must eventually call Finish() to avoid leaving |
977 | 0 | // the parser hanging. |
978 | 0 | switch (mState) { |
979 | 0 | case eInitial: { |
980 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
981 | 0 |
|
982 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) || |
983 | 0 | !OperationMayProceed()) { |
984 | 0 | FailOnNonOwningThread(); |
985 | 0 | return NS_OK; |
986 | 0 | } |
987 | 0 | |
988 | 0 | rv = InitOnMainThread(); |
989 | 0 | if (NS_FAILED(rv)) { |
990 | 0 | FailOnNonOwningThread(); |
991 | 0 | return NS_OK; |
992 | 0 | } |
993 | 0 | |
994 | 0 | mState = eWaitingToFinishInit; |
995 | 0 | MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); |
996 | 0 |
|
997 | 0 | return NS_OK; |
998 | 0 | } |
999 | 0 |
|
1000 | 0 | case eWaitingToFinishInit: { |
1001 | 0 | AssertIsOnOwningThread(); |
1002 | 0 |
|
1003 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) || |
1004 | 0 | IsActorDestroyed()) { |
1005 | 0 | Fail(); |
1006 | 0 | return NS_OK; |
1007 | 0 | } |
1008 | 0 | |
1009 | 0 | if (QuotaManager::Get()) { |
1010 | 0 | OpenDirectory(); |
1011 | 0 | return NS_OK; |
1012 | 0 | } |
1013 | 0 | |
1014 | 0 | mState = eWaitingToOpenDirectory; |
1015 | 0 | QuotaManager::GetOrCreate(this); |
1016 | 0 |
|
1017 | 0 | return NS_OK; |
1018 | 0 | } |
1019 | 0 |
|
1020 | 0 | case eWaitingToOpenDirectory: { |
1021 | 0 | AssertIsOnOwningThread(); |
1022 | 0 |
|
1023 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) || |
1024 | 0 | IsActorDestroyed()) { |
1025 | 0 | Fail(); |
1026 | 0 | return NS_OK; |
1027 | 0 | } |
1028 | 0 | |
1029 | 0 | if (NS_WARN_IF(!QuotaManager::Get())) { |
1030 | 0 | Fail(); |
1031 | 0 | return NS_OK; |
1032 | 0 | } |
1033 | 0 | |
1034 | 0 | OpenDirectory(); |
1035 | 0 | return NS_OK; |
1036 | 0 | } |
1037 | 0 |
|
1038 | 0 | case eReadyToReadMetadata: { |
1039 | 0 | AssertIsOnIOThread(); |
1040 | 0 |
|
1041 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) || |
1042 | 0 | !OperationMayProceed()) { |
1043 | 0 | FailOnNonOwningThread(); |
1044 | 0 | return NS_OK; |
1045 | 0 | } |
1046 | 0 | |
1047 | 0 | rv = ReadMetadata(); |
1048 | 0 | if (NS_FAILED(rv)) { |
1049 | 0 | FailOnNonOwningThread(); |
1050 | 0 | return NS_OK; |
1051 | 0 | } |
1052 | 0 | |
1053 | 0 | if (mOpenMode == eOpenForRead) { |
1054 | 0 | mState = eSendingMetadataForRead; |
1055 | 0 | MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); |
1056 | 0 |
|
1057 | 0 | return NS_OK; |
1058 | 0 | } |
1059 | 0 |
|
1060 | 0 | rv = OpenCacheFileForWrite(); |
1061 | 0 | if (NS_FAILED(rv)) { |
1062 | 0 | FailOnNonOwningThread(); |
1063 | 0 | return NS_OK; |
1064 | 0 | } |
1065 | 0 | |
1066 | 0 | mState = eSendingCacheFile; |
1067 | 0 | MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); |
1068 | 0 | return NS_OK; |
1069 | 0 | } |
1070 | 0 |
|
1071 | 0 | case eSendingMetadataForRead: { |
1072 | 0 | AssertIsOnOwningThread(); |
1073 | 0 | MOZ_ASSERT(mOpenMode == eOpenForRead); |
1074 | 0 |
|
1075 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) || |
1076 | 0 | IsActorDestroyed()) { |
1077 | 0 | Fail(); |
1078 | 0 | return NS_OK; |
1079 | 0 | } |
1080 | 0 | |
1081 | 0 | mState = eWaitingToOpenCacheFileForRead; |
1082 | 0 |
|
1083 | 0 | // Metadata is now open. |
1084 | 0 | if (!SendOnOpenMetadataForRead(mMetadata)) { |
1085 | 0 | Fail(); |
1086 | 0 | return NS_OK; |
1087 | 0 | } |
1088 | 0 | |
1089 | 0 | return NS_OK; |
1090 | 0 | } |
1091 | 0 |
|
1092 | 0 | case eReadyToOpenCacheFileForRead: { |
1093 | 0 | AssertIsOnIOThread(); |
1094 | 0 | MOZ_ASSERT(mOpenMode == eOpenForRead); |
1095 | 0 |
|
1096 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) || |
1097 | 0 | !OperationMayProceed()) { |
1098 | 0 | FailOnNonOwningThread(); |
1099 | 0 | return NS_OK; |
1100 | 0 | } |
1101 | 0 | |
1102 | 0 | rv = OpenCacheFileForRead(); |
1103 | 0 | if (NS_FAILED(rv)) { |
1104 | 0 | FailOnNonOwningThread(); |
1105 | 0 | return NS_OK; |
1106 | 0 | } |
1107 | 0 | |
1108 | 0 | mState = eSendingCacheFile; |
1109 | 0 | MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); |
1110 | 0 | return NS_OK; |
1111 | 0 | } |
1112 | 0 |
|
1113 | 0 | case eSendingCacheFile: { |
1114 | 0 | AssertIsOnOwningThread(); |
1115 | 0 |
|
1116 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) || |
1117 | 0 | IsActorDestroyed()) { |
1118 | 0 | Fail(); |
1119 | 0 | return NS_OK; |
1120 | 0 | } |
1121 | 0 | |
1122 | 0 | mState = eOpened; |
1123 | 0 |
|
1124 | 0 | FileDescriptor::PlatformHandleType handle = |
1125 | 0 | FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc)); |
1126 | 0 | if (!SendOnOpenCacheFile(mFileSize, FileDescriptor(handle))) { |
1127 | 0 | Fail(); |
1128 | 0 | return NS_OK; |
1129 | 0 | } |
1130 | 0 | |
1131 | 0 | // The entry is now open. |
1132 | 0 | MOZ_ASSERT(!mOpened); |
1133 | 0 | mOpened = true; |
1134 | 0 |
|
1135 | 0 | mResult = JS::AsmJSCache_Success; |
1136 | 0 |
|
1137 | 0 | return NS_OK; |
1138 | 0 | } |
1139 | 0 |
|
1140 | 0 | case eFailing: { |
1141 | 0 | AssertIsOnOwningThread(); |
1142 | 0 |
|
1143 | 0 | Fail(); |
1144 | 0 |
|
1145 | 0 | return NS_OK; |
1146 | 0 | } |
1147 | 0 |
|
1148 | 0 | case eWaitingToOpenMetadata: |
1149 | 0 | case eWaitingToOpenCacheFileForRead: |
1150 | 0 | case eOpened: |
1151 | 0 | case eFinished: { |
1152 | 0 | MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state"); |
1153 | 0 | } |
1154 | 0 | } |
1155 | 0 |
|
1156 | 0 | MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state"); |
1157 | 0 | return NS_OK; |
1158 | 0 | } |
1159 | | |
1160 | | void |
1161 | | ParentRunnable::DirectoryLockAcquired(DirectoryLock* aLock) |
1162 | 0 | { |
1163 | 0 | AssertIsOnOwningThread(); |
1164 | 0 | MOZ_ASSERT(mState == eWaitingToOpenMetadata); |
1165 | 0 | MOZ_ASSERT(!mDirectoryLock); |
1166 | 0 |
|
1167 | 0 | mDirectoryLock = aLock; |
1168 | 0 |
|
1169 | 0 | mState = eReadyToReadMetadata; |
1170 | 0 | DispatchToIOThread(); |
1171 | 0 | } |
1172 | | |
1173 | | void |
1174 | | ParentRunnable::DirectoryLockFailed() |
1175 | 0 | { |
1176 | 0 | AssertIsOnOwningThread(); |
1177 | 0 | MOZ_ASSERT(mState == eWaitingToOpenMetadata); |
1178 | 0 | MOZ_ASSERT(!mDirectoryLock); |
1179 | 0 |
|
1180 | 0 | Fail(); |
1181 | 0 | } |
1182 | | |
1183 | | NS_IMPL_ISUPPORTS_INHERITED0(ParentRunnable, FileDescriptorHolder) |
1184 | | |
1185 | | bool |
1186 | | FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams, |
1187 | | unsigned* aModuleIndex) |
1188 | 0 | { |
1189 | 0 | // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry |
1190 | 0 | // also stores an mFastHash of its first sNumFastHashChars so this gives us a |
1191 | 0 | // fast way to probabilistically determine whether we have a cache hit. We |
1192 | 0 | // still do a full hash of all the chars before returning the cache file to |
1193 | 0 | // the engine to avoid penalizing the case where there are multiple cached |
1194 | 0 | // asm.js modules where the first sNumFastHashChars are the same. The |
1195 | 0 | // mFullHash of each cache entry can have a different mNumChars so the fast |
1196 | 0 | // hash allows us to avoid performing up to Metadata::kNumEntries separate |
1197 | 0 | // full hashes. |
1198 | 0 | uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin; |
1199 | 0 | MOZ_ASSERT(numChars > sNumFastHashChars); |
1200 | 0 | uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars); |
1201 | 0 |
|
1202 | 0 | for (auto entry : aMetadata.mEntries) { |
1203 | 0 | // Compare the "fast hash" first to see whether it is worthwhile to |
1204 | 0 | // hash all the chars. |
1205 | 0 | if (entry.mFastHash != fastHash) { |
1206 | 0 | continue; |
1207 | 0 | } |
1208 | 0 | |
1209 | 0 | // Assuming we have enough characters, hash all the chars it would take |
1210 | 0 | // to match this cache entry and compare to the cache entry. If we get a |
1211 | 0 | // hit we'll still do a full source match later (in the JS engine), but |
1212 | 0 | // the full hash match means this is probably the cache entry we want. |
1213 | 0 | if (numChars < entry.mNumChars) { |
1214 | 0 | continue; |
1215 | 0 | } |
1216 | 0 | uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars); |
1217 | 0 | if (entry.mFullHash != fullHash) { |
1218 | 0 | continue; |
1219 | 0 | } |
1220 | 0 | |
1221 | 0 | *aModuleIndex = entry.mModuleIndex; |
1222 | 0 | return true; |
1223 | 0 | } |
1224 | 0 |
|
1225 | 0 | return false; |
1226 | 0 | } |
1227 | | |
1228 | | } // unnamed namespace |
1229 | | |
1230 | | PAsmJSCacheEntryParent* |
1231 | | AllocEntryParent(OpenMode aOpenMode, |
1232 | | WriteParams aWriteParams, |
1233 | | const PrincipalInfo& aPrincipalInfo) |
1234 | 0 | { |
1235 | 0 | AssertIsOnBackgroundThread(); |
1236 | 0 |
|
1237 | 0 | if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) { |
1238 | 0 | return nullptr; |
1239 | 0 | } |
1240 | 0 | |
1241 | 0 | if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) { |
1242 | 0 | MOZ_ASSERT(false); |
1243 | 0 | return nullptr; |
1244 | 0 | } |
1245 | 0 |
|
1246 | 0 | RefPtr<ParentRunnable> runnable = |
1247 | 0 | new ParentRunnable(aPrincipalInfo, aOpenMode, aWriteParams); |
1248 | 0 |
|
1249 | 0 | if (!sLiveParentActors) { |
1250 | 0 | sLiveParentActors = new ParentActorArray(); |
1251 | 0 | } |
1252 | 0 |
|
1253 | 0 | sLiveParentActors->AppendElement(runnable); |
1254 | 0 |
|
1255 | 0 | nsresult rv = NS_DispatchToMainThread(runnable); |
1256 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
1257 | 0 |
|
1258 | 0 | // Transfer ownership to IPDL. |
1259 | 0 | return runnable.forget().take(); |
1260 | 0 | } |
1261 | | |
1262 | | void |
1263 | | DeallocEntryParent(PAsmJSCacheEntryParent* aActor) |
1264 | 0 | { |
1265 | 0 | // Transfer ownership back from IPDL. |
1266 | 0 | RefPtr<ParentRunnable> op = |
1267 | 0 | dont_AddRef(static_cast<ParentRunnable*>(aActor)); |
1268 | 0 | } |
1269 | | |
1270 | | namespace { |
1271 | | |
1272 | | // A runnable that presents a single interface to the AsmJSCache ops which need |
1273 | | // to wait until the file is open. |
1274 | | class ChildRunnable final |
1275 | | : public FileDescriptorHolder |
1276 | | , public PAsmJSCacheEntryChild |
1277 | | { |
1278 | | typedef mozilla::ipc::PBackgroundChild PBackgroundChild; |
1279 | | |
1280 | | public: |
1281 | | class AutoClose |
1282 | | { |
1283 | | ChildRunnable* mChildRunnable; |
1284 | | |
1285 | | public: |
1286 | | explicit AutoClose(ChildRunnable* aChildRunnable = nullptr) |
1287 | | : mChildRunnable(aChildRunnable) |
1288 | 0 | { } |
1289 | | |
1290 | | void |
1291 | | Init(ChildRunnable* aChildRunnable) |
1292 | 0 | { |
1293 | 0 | MOZ_ASSERT(!mChildRunnable); |
1294 | 0 | mChildRunnable = aChildRunnable; |
1295 | 0 | } |
1296 | | |
1297 | | ChildRunnable* |
1298 | | operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN |
1299 | 0 | { |
1300 | 0 | MOZ_ASSERT(mChildRunnable); |
1301 | 0 | return mChildRunnable; |
1302 | 0 | } |
1303 | | |
1304 | | void |
1305 | | Forget(ChildRunnable** aChildRunnable) |
1306 | 0 | { |
1307 | 0 | *aChildRunnable = mChildRunnable; |
1308 | 0 | mChildRunnable = nullptr; |
1309 | 0 | } |
1310 | | |
1311 | | ~AutoClose() |
1312 | 0 | { |
1313 | 0 | if (mChildRunnable) { |
1314 | 0 | mChildRunnable->Close(); |
1315 | 0 | } |
1316 | 0 | } |
1317 | | }; |
1318 | | |
1319 | | NS_DECL_NSIRUNNABLE |
1320 | | |
1321 | | ChildRunnable(nsIPrincipal* aPrincipal, |
1322 | | OpenMode aOpenMode, |
1323 | | const WriteParams& aWriteParams, |
1324 | | ReadParams aReadParams) |
1325 | | : mPrincipal(aPrincipal), |
1326 | | mWriteParams(aWriteParams), |
1327 | | mReadParams(aReadParams), |
1328 | | mMutex("ChildRunnable::mMutex"), |
1329 | | mCondVar(mMutex, "ChildRunnable::mCondVar"), |
1330 | | mOpenMode(aOpenMode), |
1331 | | mState(eInitial), |
1332 | | mResult(JS::AsmJSCache_InternalError), |
1333 | | mActorDestroyed(false), |
1334 | | mWaiting(false), |
1335 | | mOpened(false) |
1336 | 0 | { |
1337 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
1338 | 0 | } |
1339 | | |
1340 | | JS::AsmJSCacheResult |
1341 | | BlockUntilOpen(AutoClose* aCloser) |
1342 | 0 | { |
1343 | 0 | MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once"); |
1344 | 0 | MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once"); |
1345 | 0 |
|
1346 | 0 | mWaiting = true; |
1347 | 0 |
|
1348 | 0 | nsresult rv = NS_DispatchToMainThread(this); |
1349 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1350 | 0 | return JS::AsmJSCache_InternalError; |
1351 | 0 | } |
1352 | 0 | |
1353 | 0 | { |
1354 | 0 | MutexAutoLock lock(mMutex); |
1355 | 0 | while (mWaiting) { |
1356 | 0 | mCondVar.Wait(); |
1357 | 0 | } |
1358 | 0 | } |
1359 | 0 |
|
1360 | 0 | if (!mOpened) { |
1361 | 0 | return mResult; |
1362 | 0 | } |
1363 | 0 | |
1364 | 0 | // Now that we're open, we're guaranteed a Close() call. However, we are |
1365 | 0 | // not guaranteed someone is holding an outstanding reference until the File |
1366 | 0 | // is closed, so we do that ourselves and Release() in OnClose(). |
1367 | 0 | aCloser->Init(this); |
1368 | 0 | AddRef(); |
1369 | 0 | return JS::AsmJSCache_Success; |
1370 | 0 | } |
1371 | | |
1372 | | void Cleanup() |
1373 | 0 | { |
1374 | | #ifdef DEBUG |
1375 | | NoteActorDestroyed(); |
1376 | | #endif |
1377 | | } |
1378 | | |
1379 | | private: |
1380 | | ~ChildRunnable() override |
1381 | 0 | { |
1382 | 0 | MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting"); |
1383 | 0 | MOZ_ASSERT(!mOpened); |
1384 | 0 | MOZ_ASSERT(mState == eFinished); |
1385 | 0 | MOZ_ASSERT(mActorDestroyed); |
1386 | 0 | } |
1387 | | |
1388 | | // IPDL methods. |
1389 | | mozilla::ipc::IPCResult |
1390 | | RecvOnOpenMetadataForRead(const Metadata& aMetadata) override |
1391 | 0 | { |
1392 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1393 | 0 | MOZ_ASSERT(mState == eOpening); |
1394 | 0 |
|
1395 | 0 | uint32_t moduleIndex; |
1396 | 0 | bool ok; |
1397 | 0 | if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) { |
1398 | 0 | ok = SendSelectCacheFileToRead(moduleIndex); |
1399 | 0 | } else { |
1400 | 0 | ok = SendSelectCacheFileToRead(JS::AsmJSCache_InternalError); |
1401 | 0 | } |
1402 | 0 | if (!ok) { |
1403 | 0 | return IPC_FAIL_NO_REASON(this); |
1404 | 0 | } |
1405 | 0 |
|
1406 | 0 | return IPC_OK(); |
1407 | 0 | } |
1408 | | |
1409 | | mozilla::ipc::IPCResult |
1410 | | RecvOnOpenCacheFile(const int64_t& aFileSize, |
1411 | | const FileDescriptor& aFileDesc) override |
1412 | 0 | { |
1413 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1414 | 0 | MOZ_ASSERT(mState == eOpening); |
1415 | 0 |
|
1416 | 0 | mFileSize = aFileSize; |
1417 | 0 |
|
1418 | 0 | auto rawFD = aFileDesc.ClonePlatformHandle(); |
1419 | 0 | mFileDesc = PR_ImportFile(PROsfd(rawFD.release())); |
1420 | 0 | if (!mFileDesc) { |
1421 | 0 | return IPC_FAIL_NO_REASON(this); |
1422 | 0 | } |
1423 | 0 |
|
1424 | 0 | mState = eOpened; |
1425 | 0 | Notify(JS::AsmJSCache_Success); |
1426 | 0 | return IPC_OK(); |
1427 | 0 | } |
1428 | | |
1429 | | mozilla::ipc::IPCResult |
1430 | | Recv__delete__(const JS::AsmJSCacheResult& aResult) override |
1431 | 0 | { |
1432 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1433 | 0 | MOZ_ASSERT(mState == eOpening || mState == eFinishing); |
1434 | 0 | MOZ_ASSERT_IF(mState == eOpening, aResult != JS::AsmJSCache_Success); |
1435 | 0 | MOZ_ASSERT_IF(mState == eFinishing, aResult == JS::AsmJSCache_Success); |
1436 | 0 |
|
1437 | 0 | if (mState == eOpening) { |
1438 | 0 | Fail(aResult); |
1439 | 0 | } else { |
1440 | 0 | // Match the AddRef in BlockUntilOpen(). The IPDL still holds an |
1441 | 0 | // outstanding ref which will keep 'this' alive until ActorDestroy() |
1442 | 0 | // is executed. |
1443 | 0 | Release(); |
1444 | 0 |
|
1445 | 0 | mState = eFinished; |
1446 | 0 | } |
1447 | 0 | return IPC_OK(); |
1448 | 0 | } |
1449 | | |
1450 | | void |
1451 | | ActorDestroy(ActorDestroyReason why) override |
1452 | 0 | { |
1453 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1454 | 0 | NoteActorDestroyed(); |
1455 | 0 | } |
1456 | | |
1457 | | void |
1458 | | Close() |
1459 | 0 | { |
1460 | 0 | MOZ_ASSERT(mState == eOpened); |
1461 | 0 |
|
1462 | 0 | mState = eClosing; |
1463 | 0 | NS_DispatchToMainThread(this); |
1464 | 0 | } |
1465 | | |
1466 | | void |
1467 | | Fail(JS::AsmJSCacheResult aResult) |
1468 | 0 | { |
1469 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1470 | 0 | MOZ_ASSERT(mState == eInitial || mState == eOpening); |
1471 | 0 | MOZ_ASSERT(aResult != JS::AsmJSCache_Success); |
1472 | 0 |
|
1473 | 0 | mState = eFinished; |
1474 | 0 |
|
1475 | 0 | FileDescriptorHolder::Finish(); |
1476 | 0 | Notify(aResult); |
1477 | 0 | } |
1478 | | |
1479 | | void |
1480 | | Notify(JS::AsmJSCacheResult aResult) |
1481 | 0 | { |
1482 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1483 | 0 |
|
1484 | 0 | MutexAutoLock lock(mMutex); |
1485 | 0 | MOZ_ASSERT(mWaiting); |
1486 | 0 |
|
1487 | 0 | mWaiting = false; |
1488 | 0 | mOpened = aResult == JS::AsmJSCache_Success; |
1489 | 0 | mResult = aResult; |
1490 | 0 | mCondVar.Notify(); |
1491 | 0 | } |
1492 | | |
1493 | | void NoteActorDestroyed() |
1494 | 0 | { |
1495 | 0 | mActorDestroyed = true; |
1496 | 0 | } |
1497 | | |
1498 | | nsIPrincipal* const mPrincipal; |
1499 | | nsAutoPtr<PrincipalInfo> mPrincipalInfo; |
1500 | | WriteParams mWriteParams; |
1501 | | ReadParams mReadParams; |
1502 | | Mutex mMutex; |
1503 | | CondVar mCondVar; |
1504 | | |
1505 | | // Couple enums and bools together |
1506 | | const OpenMode mOpenMode; |
1507 | | enum State { |
1508 | | eInitial, // Just created, waiting to be dispatched to the main thread |
1509 | | eOpening, // Waiting for the parent process to respond |
1510 | | eOpened, // Parent process opened the entry and sent it back |
1511 | | eClosing, // Waiting to be dispatched to the main thread to Send__delete__ |
1512 | | eFinishing, // Waiting for the parent process to close |
1513 | | eFinished // Terminal state |
1514 | | }; |
1515 | | State mState; |
1516 | | JS::AsmJSCacheResult mResult; |
1517 | | |
1518 | | bool mActorDestroyed; |
1519 | | bool mWaiting; |
1520 | | bool mOpened; |
1521 | | }; |
1522 | | |
1523 | | NS_IMETHODIMP |
1524 | | ChildRunnable::Run() |
1525 | 0 | { |
1526 | 0 | switch (mState) { |
1527 | 0 | case eInitial: { |
1528 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1529 | 0 |
|
1530 | 0 | if (mPrincipal->GetIsNullPrincipal()) { |
1531 | 0 | NS_WARNING("AsmsJSCache not supported on null principal."); |
1532 | 0 | Fail(JS::AsmJSCache_InternalError); |
1533 | 0 | return NS_OK; |
1534 | 0 | } |
1535 | 0 |
|
1536 | 0 | nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo()); |
1537 | 0 | nsresult rv = PrincipalToPrincipalInfo(mPrincipal, principalInfo); |
1538 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1539 | 0 | Fail(JS::AsmJSCache_InternalError); |
1540 | 0 | return NS_OK; |
1541 | 0 | } |
1542 | 0 | |
1543 | 0 | mPrincipalInfo = std::move(principalInfo); |
1544 | 0 |
|
1545 | 0 | PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread(); |
1546 | 0 | if (NS_WARN_IF(!actor)) { |
1547 | 0 | Fail(JS::AsmJSCache_InternalError); |
1548 | 0 | return NS_OK; |
1549 | 0 | } |
1550 | 0 | |
1551 | 0 | if (!actor->SendPAsmJSCacheEntryConstructor(this, mOpenMode, mWriteParams, |
1552 | 0 | *mPrincipalInfo)) { |
1553 | 0 | // Unblock the parsing thread with a failure. |
1554 | 0 |
|
1555 | 0 | Fail(JS::AsmJSCache_InternalError); |
1556 | 0 | return NS_OK; |
1557 | 0 | } |
1558 | 0 | |
1559 | 0 | // AddRef to keep this runnable alive until IPDL deallocates the |
1560 | 0 | // subprotocol (DeallocEntryChild). |
1561 | 0 | AddRef(); |
1562 | 0 |
|
1563 | 0 | mState = eOpening; |
1564 | 0 | return NS_OK; |
1565 | 0 | } |
1566 | 0 |
|
1567 | 0 | case eClosing: { |
1568 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1569 | 0 |
|
1570 | 0 | // Per FileDescriptorHolder::Finish()'s comment, call before |
1571 | 0 | // releasing the directory lock (which happens in the parent upon receipt |
1572 | 0 | // of the Close message). |
1573 | 0 | FileDescriptorHolder::Finish(); |
1574 | 0 |
|
1575 | 0 | MOZ_ASSERT(mOpened); |
1576 | 0 | mOpened = false; |
1577 | 0 |
|
1578 | 0 | if (mActorDestroyed) { |
1579 | 0 | // Match the AddRef in BlockUntilOpen(). The main thread event loop |
1580 | 0 | // still holds an outstanding ref which will keep 'this' alive until |
1581 | 0 | // returning to the event loop. |
1582 | 0 | Release(); |
1583 | 0 |
|
1584 | 0 | mState = eFinished; |
1585 | 0 | } else { |
1586 | 0 | Unused << SendClose(); |
1587 | 0 |
|
1588 | 0 | mState = eFinishing; |
1589 | 0 | } |
1590 | 0 |
|
1591 | 0 | return NS_OK; |
1592 | 0 | } |
1593 | 0 |
|
1594 | 0 | case eOpening: |
1595 | 0 | case eOpened: |
1596 | 0 | case eFinishing: |
1597 | 0 | case eFinished: { |
1598 | 0 | MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state"); |
1599 | 0 | } |
1600 | 0 | } |
1601 | 0 |
|
1602 | 0 | MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state"); |
1603 | 0 | return NS_OK; |
1604 | 0 | } |
1605 | | |
1606 | | } // unnamed namespace |
1607 | | |
1608 | | void |
1609 | | DeallocEntryChild(PAsmJSCacheEntryChild* aActor) |
1610 | 0 | { |
1611 | 0 | // Match the AddRef before SendPAsmJSCacheEntryConstructor. |
1612 | 0 | static_cast<ChildRunnable*>(aActor)->Release(); |
1613 | 0 | } |
1614 | | |
1615 | | namespace { |
1616 | | |
1617 | | JS::AsmJSCacheResult |
1618 | | OpenFile(nsIPrincipal* aPrincipal, |
1619 | | OpenMode aOpenMode, |
1620 | | WriteParams aWriteParams, |
1621 | | ReadParams aReadParams, |
1622 | | ChildRunnable::AutoClose* aChildRunnable) |
1623 | 0 | { |
1624 | 0 | MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0); |
1625 | 0 | MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr); |
1626 | 0 |
|
1627 | 0 | // There are three reasons we don't attempt caching from the main thread: |
1628 | 0 | // 1. In the parent process: QuotaManager::WaitForOpenAllowed prevents |
1629 | 0 | // synchronous waiting on the main thread requiring a runnable to be |
1630 | 0 | // dispatched to the main thread. |
1631 | 0 | // 2. In the child process: the IPDL PContent messages we need to |
1632 | 0 | // synchronously wait on are dispatched to the main thread. |
1633 | 0 | // 3. While a cache lookup *should* be much faster than compilation, IO |
1634 | 0 | // operations can be unpredictably slow and we'd like to avoid the |
1635 | 0 | // occasional janks on the main thread. |
1636 | 0 | // We could use a nested event loop to address 1 and 2, but we're potentially |
1637 | 0 | // in the middle of running JS (eval()) and nested event loops can be |
1638 | 0 | // semantically observable. |
1639 | 0 | if (NS_IsMainThread()) { |
1640 | 0 | return JS::AsmJSCache_SynchronousScript; |
1641 | 0 | } |
1642 | 0 | |
1643 | 0 | // Check to see whether the principal reflects a private browsing session. |
1644 | 0 | // Since AsmJSCache requires disk access at the moment, caching should be |
1645 | 0 | // disabled in private browsing situations. Failing here will cause later |
1646 | 0 | // read/write requests to also fail. |
1647 | 0 | uint32_t pbId; |
1648 | 0 | if (NS_WARN_IF(NS_FAILED(aPrincipal->GetPrivateBrowsingId(&pbId)))) { |
1649 | 0 | return JS::AsmJSCache_InternalError; |
1650 | 0 | } |
1651 | 0 | |
1652 | 0 | if (pbId > 0) { |
1653 | 0 | return JS::AsmJSCache_Disabled_PrivateBrowsing; |
1654 | 0 | } |
1655 | 0 | |
1656 | 0 | // We need to synchronously call into the parent to open the file and |
1657 | 0 | // interact with the QuotaManager. The child can then map the file into its |
1658 | 0 | // address space to perform I/O. |
1659 | 0 | RefPtr<ChildRunnable> childRunnable = |
1660 | 0 | new ChildRunnable(aPrincipal, aOpenMode, aWriteParams, aReadParams); |
1661 | 0 |
|
1662 | 0 | JS::AsmJSCacheResult openResult = |
1663 | 0 | childRunnable->BlockUntilOpen(aChildRunnable); |
1664 | 0 | if (openResult != JS::AsmJSCache_Success) { |
1665 | 0 | childRunnable->Cleanup(); |
1666 | 0 | return openResult; |
1667 | 0 | } |
1668 | 0 | |
1669 | 0 | if (!childRunnable->MapMemory(aOpenMode)) { |
1670 | 0 | return JS::AsmJSCache_InternalError; |
1671 | 0 | } |
1672 | 0 | |
1673 | 0 | return JS::AsmJSCache_Success; |
1674 | 0 | } |
1675 | | |
1676 | | } // namespace |
1677 | | |
1678 | | typedef uint32_t AsmJSCookieType; |
1679 | | static const uint32_t sAsmJSCookie = 0x600d600d; |
1680 | | |
1681 | | bool |
1682 | | OpenEntryForRead(nsIPrincipal* aPrincipal, |
1683 | | const char16_t* aBegin, |
1684 | | const char16_t* aLimit, |
1685 | | size_t* aSize, |
1686 | | const uint8_t** aMemory, |
1687 | | intptr_t* aHandle) |
1688 | 0 | { |
1689 | 0 | if (size_t(aLimit - aBegin) < sMinCachedModuleLength) { |
1690 | 0 | return false; |
1691 | 0 | } |
1692 | 0 | |
1693 | 0 | ReadParams readParams; |
1694 | 0 | readParams.mBegin = aBegin; |
1695 | 0 | readParams.mLimit = aLimit; |
1696 | 0 |
|
1697 | 0 | ChildRunnable::AutoClose childRunnable; |
1698 | 0 | WriteParams notAWrite; |
1699 | 0 | JS::AsmJSCacheResult openResult = |
1700 | 0 | OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &childRunnable); |
1701 | 0 | if (openResult != JS::AsmJSCache_Success) { |
1702 | 0 | return false; |
1703 | 0 | } |
1704 | 0 | |
1705 | 0 | // Although we trust that the stored cache files have not been arbitrarily |
1706 | 0 | // corrupted, it is possible that a previous execution aborted in the middle |
1707 | 0 | // of writing a cache file (crash, OOM-killer, etc). To protect against |
1708 | 0 | // partially-written cache files, we use the following scheme: |
1709 | 0 | // - Allocate an extra word at the beginning of every cache file which |
1710 | 0 | // starts out 0 (OpenFile opens with PR_TRUNCATE). |
1711 | 0 | // - After the asm.js serialization is complete, PR_SyncMemMap to write |
1712 | 0 | // everything to disk and then store a non-zero value (sAsmJSCookie) |
1713 | 0 | // in the first word. |
1714 | 0 | // - When attempting to read a cache file, check whether the first word is |
1715 | 0 | // sAsmJSCookie. |
1716 | 0 | if (childRunnable->FileSize() < sizeof(AsmJSCookieType) || |
1717 | 0 | *(AsmJSCookieType*)childRunnable->MappedMemory() != sAsmJSCookie) { |
1718 | 0 | return false; |
1719 | 0 | } |
1720 | 0 | |
1721 | 0 | *aSize = childRunnable->FileSize() - sizeof(AsmJSCookieType); |
1722 | 0 | *aMemory = (uint8_t*) childRunnable->MappedMemory() + sizeof(AsmJSCookieType); |
1723 | 0 |
|
1724 | 0 | // The caller guarnatees a call to CloseEntryForRead (on success or |
1725 | 0 | // failure) at which point the file will be closed. |
1726 | 0 | childRunnable.Forget(reinterpret_cast<ChildRunnable**>(aHandle)); |
1727 | 0 | return true; |
1728 | 0 | } |
1729 | | |
1730 | | void |
1731 | | CloseEntryForRead(size_t aSize, |
1732 | | const uint8_t* aMemory, |
1733 | | intptr_t aHandle) |
1734 | 0 | { |
1735 | 0 | ChildRunnable::AutoClose childRunnable( |
1736 | 0 | reinterpret_cast<ChildRunnable*>(aHandle)); |
1737 | 0 |
|
1738 | 0 | MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize()); |
1739 | 0 | MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == |
1740 | 0 | childRunnable->MappedMemory()); |
1741 | 0 | } |
1742 | | |
1743 | | JS::AsmJSCacheResult |
1744 | | OpenEntryForWrite(nsIPrincipal* aPrincipal, |
1745 | | const char16_t* aBegin, |
1746 | | const char16_t* aEnd, |
1747 | | size_t aSize, |
1748 | | uint8_t** aMemory, |
1749 | | intptr_t* aHandle) |
1750 | 0 | { |
1751 | 0 | if (size_t(aEnd - aBegin) < sMinCachedModuleLength) { |
1752 | 0 | return JS::AsmJSCache_ModuleTooSmall; |
1753 | 0 | } |
1754 | 0 | |
1755 | 0 | // Add extra space for the AsmJSCookieType (see OpenEntryForRead). |
1756 | 0 | aSize += sizeof(AsmJSCookieType); |
1757 | 0 |
|
1758 | 0 | static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe"); |
1759 | 0 |
|
1760 | 0 | WriteParams writeParams; |
1761 | 0 | writeParams.mSize = aSize; |
1762 | 0 | writeParams.mFastHash = HashString(aBegin, sNumFastHashChars); |
1763 | 0 | writeParams.mNumChars = aEnd - aBegin; |
1764 | 0 | writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars); |
1765 | 0 |
|
1766 | 0 | ChildRunnable::AutoClose childRunnable; |
1767 | 0 | ReadParams notARead; |
1768 | 0 | JS::AsmJSCacheResult openResult = |
1769 | 0 | OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &childRunnable); |
1770 | 0 | if (openResult != JS::AsmJSCache_Success) { |
1771 | 0 | return openResult; |
1772 | 0 | } |
1773 | 0 | |
1774 | 0 | // Strip off the AsmJSCookieType from the buffer returned to the caller, |
1775 | 0 | // which expects a buffer of aSize, not a buffer of sizeWithCookie starting |
1776 | 0 | // with a cookie. |
1777 | 0 | *aMemory = (uint8_t*) childRunnable->MappedMemory() + sizeof(AsmJSCookieType); |
1778 | 0 |
|
1779 | 0 | // The caller guarnatees a call to CloseEntryForWrite (on success or |
1780 | 0 | // failure) at which point the file will be closed |
1781 | 0 | childRunnable.Forget(reinterpret_cast<ChildRunnable**>(aHandle)); |
1782 | 0 | return JS::AsmJSCache_Success; |
1783 | 0 | } |
1784 | | |
1785 | | void |
1786 | | CloseEntryForWrite(size_t aSize, |
1787 | | uint8_t* aMemory, |
1788 | | intptr_t aHandle) |
1789 | 0 | { |
1790 | 0 | ChildRunnable::AutoClose childRunnable( |
1791 | 0 | reinterpret_cast<ChildRunnable*>(aHandle)); |
1792 | 0 |
|
1793 | 0 | MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize()); |
1794 | 0 | MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == |
1795 | 0 | childRunnable->MappedMemory()); |
1796 | 0 |
|
1797 | 0 | // Flush to disk before writing the cookie (see OpenEntryForRead). |
1798 | 0 | if (PR_SyncMemMap(childRunnable->FileDesc(), |
1799 | 0 | childRunnable->MappedMemory(), |
1800 | 0 | childRunnable->FileSize()) == PR_SUCCESS) { |
1801 | 0 | *(AsmJSCookieType*)childRunnable->MappedMemory() = sAsmJSCookie; |
1802 | 0 | } |
1803 | 0 | } |
1804 | | |
1805 | | /******************************************************************************* |
1806 | | * Client |
1807 | | ******************************************************************************/ |
1808 | | |
1809 | | Client* Client::sInstance = nullptr; |
1810 | | |
1811 | | Client::Client() |
1812 | | : mShutdownRequested(false) |
1813 | 0 | { |
1814 | 0 | AssertIsOnBackgroundThread(); |
1815 | 0 | MOZ_ASSERT(!sInstance, "We expect this to be a singleton!"); |
1816 | 0 |
|
1817 | 0 | sInstance = this; |
1818 | 0 | } |
1819 | | |
1820 | | Client::~Client() |
1821 | 0 | { |
1822 | 0 | AssertIsOnBackgroundThread(); |
1823 | 0 | MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!"); |
1824 | 0 |
|
1825 | 0 | sInstance = nullptr; |
1826 | 0 | } |
1827 | | |
1828 | | Client::Type |
1829 | | Client::GetType() |
1830 | 0 | { |
1831 | 0 | return ASMJS; |
1832 | 0 | } |
1833 | | |
1834 | | nsresult |
1835 | | Client::InitOrigin(PersistenceType aPersistenceType, |
1836 | | const nsACString& aGroup, |
1837 | | const nsACString& aOrigin, |
1838 | | const AtomicBool& aCanceled, |
1839 | | UsageInfo* aUsageInfo) |
1840 | 0 | { |
1841 | 0 | if (!aUsageInfo) { |
1842 | 0 | return NS_OK; |
1843 | 0 | } |
1844 | 0 | return GetUsageForOrigin(aPersistenceType, |
1845 | 0 | aGroup, |
1846 | 0 | aOrigin, |
1847 | 0 | aCanceled, |
1848 | 0 | aUsageInfo); |
1849 | 0 | } |
1850 | | |
1851 | | nsresult |
1852 | | Client::GetUsageForOrigin(PersistenceType aPersistenceType, |
1853 | | const nsACString& aGroup, |
1854 | | const nsACString& aOrigin, |
1855 | | const AtomicBool& aCanceled, |
1856 | | UsageInfo* aUsageInfo) |
1857 | 0 | { |
1858 | 0 | QuotaManager* qm = QuotaManager::Get(); |
1859 | 0 | MOZ_ASSERT(qm, "We were being called by the QuotaManager"); |
1860 | 0 |
|
1861 | 0 | nsCOMPtr<nsIFile> directory; |
1862 | 0 | nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin, |
1863 | 0 | getter_AddRefs(directory)); |
1864 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1865 | 0 | return rv; |
1866 | 0 | } |
1867 | 0 | |
1868 | 0 | MOZ_ASSERT(directory, "We're here because the origin directory exists"); |
1869 | 0 |
|
1870 | 0 | rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); |
1871 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1872 | 0 | return rv; |
1873 | 0 | } |
1874 | 0 | |
1875 | 0 | DebugOnly<bool> exists; |
1876 | 0 | MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); |
1877 | 0 |
|
1878 | 0 | nsCOMPtr<nsIDirectoryEnumerator> entries; |
1879 | 0 | rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); |
1880 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1881 | 0 | return rv; |
1882 | 0 | } |
1883 | 0 | |
1884 | 0 | nsCOMPtr<nsIFile> file; |
1885 | 0 | while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) && |
1886 | 0 | file && !aCanceled) { |
1887 | 0 | int64_t fileSize; |
1888 | 0 | rv = file->GetFileSize(&fileSize); |
1889 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1890 | 0 | return rv; |
1891 | 0 | } |
1892 | 0 | |
1893 | 0 | MOZ_ASSERT(fileSize >= 0, "Negative size?!"); |
1894 | 0 |
|
1895 | 0 | // Since the client is not explicitly storing files, append to database |
1896 | 0 | // usage which represents implicit storage allocation. |
1897 | 0 | aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); |
1898 | 0 | } |
1899 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1900 | 0 | return rv; |
1901 | 0 | } |
1902 | 0 | |
1903 | 0 | return NS_OK; |
1904 | 0 | } |
1905 | | |
1906 | | void |
1907 | | Client::OnOriginClearCompleted(PersistenceType aPersistenceType, |
1908 | | const nsACString& aOrigin) |
1909 | 0 | { |
1910 | 0 | } |
1911 | | |
1912 | | void |
1913 | | Client::ReleaseIOThreadObjects() |
1914 | 0 | { |
1915 | 0 | } |
1916 | | |
1917 | | void |
1918 | | Client::AbortOperations(const nsACString& aOrigin) |
1919 | 0 | { |
1920 | 0 | } |
1921 | | |
1922 | | void |
1923 | | Client::AbortOperationsForProcess(ContentParentId aContentParentId) |
1924 | 0 | { |
1925 | 0 | } |
1926 | | |
1927 | | void |
1928 | | Client::StartIdleMaintenance() |
1929 | 0 | { |
1930 | 0 | } |
1931 | | |
1932 | | void |
1933 | | Client::StopIdleMaintenance() |
1934 | 0 | { |
1935 | 0 | } |
1936 | | |
1937 | | void |
1938 | | Client::ShutdownWorkThreads() |
1939 | 0 | { |
1940 | 0 | AssertIsOnBackgroundThread(); |
1941 | 0 | MOZ_ASSERT(!mShutdownRequested); |
1942 | 0 |
|
1943 | 0 | mShutdownRequested = true; |
1944 | 0 |
|
1945 | 0 | if (sLiveParentActors) { |
1946 | 0 | MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { |
1947 | 0 | return !sLiveParentActors; |
1948 | 0 | })); |
1949 | 0 | } |
1950 | 0 | } |
1951 | | |
1952 | | quota::Client* |
1953 | | CreateClient() |
1954 | 0 | { |
1955 | 0 | return new Client(); |
1956 | 0 | } |
1957 | | |
1958 | | } // namespace asmjscache |
1959 | | } // namespace dom |
1960 | | } // namespace mozilla |
1961 | | |
1962 | | namespace IPC { |
1963 | | |
1964 | | using mozilla::dom::asmjscache::Metadata; |
1965 | | using mozilla::dom::asmjscache::WriteParams; |
1966 | | |
1967 | | void |
1968 | | ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam) |
1969 | 0 | { |
1970 | 0 | for (auto entry : aParam.mEntries) { |
1971 | 0 | WriteParam(aMsg, entry.mFastHash); |
1972 | 0 | WriteParam(aMsg, entry.mNumChars); |
1973 | 0 | WriteParam(aMsg, entry.mFullHash); |
1974 | 0 | WriteParam(aMsg, entry.mModuleIndex); |
1975 | 0 | } |
1976 | 0 | } |
1977 | | |
1978 | | bool |
1979 | | ParamTraits<Metadata>::Read(const Message* aMsg, PickleIterator* aIter, |
1980 | | paramType* aResult) |
1981 | 0 | { |
1982 | 0 | for (auto& entry : aResult->mEntries) { |
1983 | 0 | if (!ReadParam(aMsg, aIter, &entry.mFastHash) || |
1984 | 0 | !ReadParam(aMsg, aIter, &entry.mNumChars) || |
1985 | 0 | !ReadParam(aMsg, aIter, &entry.mFullHash) || |
1986 | 0 | !ReadParam(aMsg, aIter, &entry.mModuleIndex)) |
1987 | 0 | { |
1988 | 0 | return false; |
1989 | 0 | } |
1990 | 0 | } |
1991 | 0 | return true; |
1992 | 0 | } |
1993 | | |
1994 | | void |
1995 | | ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog) |
1996 | 0 | { |
1997 | 0 | for (auto entry : aParam.mEntries) { |
1998 | 0 | LogParam(entry.mFastHash, aLog); |
1999 | 0 | LogParam(entry.mNumChars, aLog); |
2000 | 0 | LogParam(entry.mFullHash, aLog); |
2001 | 0 | LogParam(entry.mModuleIndex, aLog); |
2002 | 0 | } |
2003 | 0 | } |
2004 | | |
2005 | | void |
2006 | | ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam) |
2007 | 0 | { |
2008 | 0 | WriteParam(aMsg, aParam.mSize); |
2009 | 0 | WriteParam(aMsg, aParam.mFastHash); |
2010 | 0 | WriteParam(aMsg, aParam.mNumChars); |
2011 | 0 | WriteParam(aMsg, aParam.mFullHash); |
2012 | 0 | } |
2013 | | |
2014 | | bool |
2015 | | ParamTraits<WriteParams>::Read(const Message* aMsg, PickleIterator* aIter, |
2016 | | paramType* aResult) |
2017 | 0 | { |
2018 | 0 | return ReadParam(aMsg, aIter, &aResult->mSize) && |
2019 | 0 | ReadParam(aMsg, aIter, &aResult->mFastHash) && |
2020 | 0 | ReadParam(aMsg, aIter, &aResult->mNumChars) && |
2021 | 0 | ReadParam(aMsg, aIter, &aResult->mFullHash); |
2022 | 0 | } |
2023 | | |
2024 | | void |
2025 | | ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog) |
2026 | 0 | { |
2027 | 0 | LogParam(aParam.mSize, aLog); |
2028 | 0 | LogParam(aParam.mFastHash, aLog); |
2029 | 0 | LogParam(aParam.mNumChars, aLog); |
2030 | 0 | LogParam(aParam.mFullHash, aLog); |
2031 | 0 | } |
2032 | | |
2033 | | } // namespace IPC |