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