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(&currentBuildId);
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(&current)) &&
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