Bug 1404274 - Key Evaluation on the cloned JS objects. r=asuth, a=IanN CLOSED TREE DONTBUILD SEAMONKEY_2_49_ESR_RELBRANCH
authorJan Varga <jan.varga@gmail.com>
Tue, 24 Jul 2018 18:19:10 +0200
branchSEAMONKEY_2_49_ESR_RELBRANCH
changeset 357536 c88a220931ae59e6270686bb0232f9a6fbcc3bab
parent 357535 f8828ed8d6836a04e990eee5330610fe467129ef
child 357537 eb92d7b3043c75933192cca237f7a9fe32c69ebb
push id7834
push userfrgrahl@gmx.net
push dateSun, 13 Jan 2019 12:17:02 +0000
treeherdermozilla-esr52@6e4ad8a8f2e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, IanN
bugs1404274
milestone52.9.1
Bug 1404274 - Key Evaluation on the cloned JS objects. r=asuth, a=IanN CLOSED TREE DONTBUILD mozilla-esr52 SEAMONKEY_2_49_ESR_RELBRANCH
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -714,54 +714,61 @@ IDBCursor::Update(JSContext* aCx, JS::Ha
   if (mType == Type_ObjectStore) {
     objectStore = mSourceObjectStore;
   } else {
     objectStore = mSourceIndex->ObjectStore();
   }
 
   MOZ_ASSERT(objectStore);
 
+  IDBObjectStore::ValueWrapper valueWrapper(aCx, aValue);
+
   const Key& primaryKey = (mType == Type_ObjectStore) ? mKey : mPrimaryKey;
 
   RefPtr<IDBRequest> request;
 
   if (objectStore->HasValidKeyPath()) {
+    if (!valueWrapper.Clone(aCx)) {
+      aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+      return nullptr;
+    }
+
     // Make sure the object given has the correct keyPath value set on it.
     const KeyPath& keyPath = objectStore->GetKeyPath();
     Key key;
 
-    aRv = keyPath.ExtractKey(aCx, aValue, key);
+    aRv = keyPath.ExtractKey(aCx, valueWrapper.Value(), key);
     if (aRv.Failed()) {
       return nullptr;
     }
 
     if (key != primaryKey) {
       aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
       return nullptr;
     }
 
     request = objectStore->AddOrPut(aCx,
-                                    aValue,
+                                    valueWrapper,
                                     /* aKey */ JS::UndefinedHandleValue,
                                     /* aOverwrite */ true,
                                     /* aFromCursor */ true,
                                     aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
   }
   else {
     JS::Rooted<JS::Value> keyVal(aCx);
     aRv = primaryKey.ToJSVal(aCx, &keyVal);
     if (aRv.Failed()) {
       return nullptr;
     }
 
     request = objectStore->AddOrPut(aCx,
-                                    aValue,
+                                    valueWrapper,
                                     keyVal,
                                     /* aOverwrite */ true,
                                     /* aFromCursor */ true,
                                     aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
   }
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -91,16 +91,29 @@ struct IDBObjectStore::StructuredCloneWr
   }
 
   ~StructuredCloneWriteInfo()
   {
     MOZ_COUNT_DTOR(StructuredCloneWriteInfo);
   }
 };
 
+// Used by ValueWrapper::Clone to hold strong references to any blob-like
+// objects through the clone process.  This is necessary because:
+// - The structured clone process may trigger content code via getters/other
+//   which can potentially cause existing strong references to be dropped,
+//   necessitating the clone to hold its own strong references.
+// - The structured clone can abort partway through, so it's necessary to track
+//   what strong references have been acquired so that they can be freed even
+//   if a de-serialization does not occur.
+struct IDBObjectStore::StructuredCloneInfo
+{
+  nsTArray<StructuredCloneFile> mFiles;
+};
+
 namespace {
 
 struct MOZ_STACK_CLASS MutableFileData final
 {
   nsString type;
   nsString name;
 
   MutableFileData()
@@ -397,16 +410,105 @@ StructuredCloneWriteCallback(JSContext* 
     newFile->mType = StructuredCloneFile::eWasmCompiled;
 
     return true;
   }
 
   return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter, aObj);
 }
 
+bool
+CopyingStructuredCloneWriteCallback(JSContext* aCx,
+                                    JSStructuredCloneWriter* aWriter,
+                                    JS::Handle<JSObject*> aObj,
+                                    void* aClosure)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aWriter);
+  MOZ_ASSERT(aClosure);
+
+  auto* cloneInfo = static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
+
+  // UNWRAP_OBJECT calls might mutate this.
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
+  {
+    Blob* blob = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
+      if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
+        MOZ_ASSERT(false,
+                   "Fix the structured clone data to use a bigger type!");
+        return false;
+      }
+
+      const uint32_t index = cloneInfo->mFiles.Length();
+
+      if (!JS_WriteUint32Pair(aWriter,
+                              blob->IsFile() ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB,
+                              index)) {
+        return false;
+      }
+
+      StructuredCloneFile* newFile = cloneInfo->mFiles.AppendElement();
+      newFile->mBlob = blob;
+      newFile->mType = StructuredCloneFile::eBlob;
+
+      return true;
+    }
+  }
+
+  {
+    IDBMutableFile* mutableFile;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
+      if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
+        MOZ_ASSERT(false,
+                   "Fix the structured clone data to use a bigger type!");
+        return false;
+      }
+
+      const uint32_t index = cloneInfo->mFiles.Length();
+
+      if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_MUTABLEFILE, index)) {
+        return false;
+      }
+
+      StructuredCloneFile* newFile = cloneInfo->mFiles.AppendElement();
+      newFile->mMutableFile = mutableFile;
+      newFile->mType = StructuredCloneFile::eMutableFile;
+
+      return true;
+    }
+  }
+
+  if (JS::IsWasmModuleObject(aObj)) {
+    RefPtr<JS::WasmModule> module = JS::GetWasmModule(aObj);
+    MOZ_ASSERT(module);
+
+    if (cloneInfo->mFiles.Length() > size_t(UINT32_MAX)) {
+      MOZ_ASSERT(false,
+                 "Fix the structured clone data to use a bigger type!");
+      return false;
+    }
+
+    const uint32_t index = cloneInfo->mFiles.Length();
+
+    if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_WASM, index)) {
+      return false;
+    }
+
+    StructuredCloneFile* newFile = cloneInfo->mFiles.AppendElement();
+    newFile->mWasmModule = module;
+    newFile->mType = StructuredCloneFile::eWasmBytecode;
+
+    return true;
+  }
+
+  return StructuredCloneHolder::WriteFullySerializableObjects(aCx, aWriter, aObj);
+}
+
 nsresult
 GetAddInfoCallback(JSContext* aCx, void* aClosure)
 {
   static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = {
     nullptr /* read */,
     StructuredCloneWriteCallback /* write */,
     nullptr /* reportError */,
     nullptr /* readTransfer */,
@@ -1023,16 +1125,109 @@ CommonStructuredCloneReadCallback(JSCont
 
     return result;
   }
 
   return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
                                                              aTag);
 }
 
+JSObject*
+CopyingStructuredCloneReadCallback(JSContext* aCx,
+                                   JSStructuredCloneReader* aReader,
+                                   uint32_t aTag,
+                                   uint32_t aData,
+                                   void* aClosure)
+{
+  MOZ_ASSERT(aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE);
+
+  if (aTag == SCTAG_DOM_BLOB ||
+      aTag == SCTAG_DOM_FILE ||
+      aTag == SCTAG_DOM_MUTABLEFILE ||
+      aTag == SCTAG_DOM_WASM) {
+    auto* cloneInfo =
+      static_cast<IDBObjectStore::StructuredCloneInfo*>(aClosure);
+
+    JS::Rooted<JSObject*> result(aCx);
+
+    if (aData >= cloneInfo->mFiles.Length()) {
+      MOZ_ASSERT(false, "Bad index value!");
+      return nullptr;
+    }
+
+    StructuredCloneFile& file = cloneInfo->mFiles[aData];
+
+    if (aTag == SCTAG_DOM_BLOB) {
+      MOZ_ASSERT(file.mType == StructuredCloneFile::eBlob);
+
+      RefPtr<Blob> blob = file.mBlob;
+      MOZ_ASSERT(!blob->IsFile());
+
+      JS::Rooted<JS::Value> wrappedBlob(aCx);
+      if (NS_WARN_IF(!ToJSValue(aCx, blob, &wrappedBlob))) {
+        return nullptr;
+      }
+
+      result.set(&wrappedBlob.toObject());
+
+      return result;
+    }
+
+    if (aTag == SCTAG_DOM_FILE) {
+      MOZ_ASSERT(file.mType == StructuredCloneFile::eBlob);
+
+      RefPtr<Blob> blob = file.mBlob;
+      MOZ_ASSERT(blob->IsFile());
+
+      RefPtr<File> file = blob->ToFile();
+      MOZ_ASSERT(file);
+
+      JS::Rooted<JS::Value> wrappedFile(aCx);
+      if (NS_WARN_IF(!ToJSValue(aCx, file, &wrappedFile))) {
+        return nullptr;
+      }
+
+      result.set(&wrappedFile.toObject());
+
+      return result;
+    }
+
+    if (aTag == SCTAG_DOM_MUTABLEFILE) {
+      MOZ_ASSERT(file.mType == StructuredCloneFile::eMutableFile);
+
+      RefPtr<IDBMutableFile> mutableFile = file.mMutableFile;
+
+      JS::Rooted<JS::Value> wrappedMutableFile(aCx);
+      if (NS_WARN_IF(!ToJSValue(aCx, mutableFile, &wrappedMutableFile))) {
+        return nullptr;
+      }
+
+      result.set(&wrappedMutableFile.toObject());
+
+      return result;
+    }
+
+    MOZ_ASSERT(file.mType == StructuredCloneFile::eWasmBytecode);
+
+    RefPtr<JS::WasmModule> module = file.mWasmModule;
+
+    JS::Rooted<JSObject*> wrappedModule(aCx, module->createObject(aCx));
+    if (NS_WARN_IF(!wrappedModule)) {
+      return nullptr;
+    }
+
+    result.set(wrappedModule);
+
+    return result;
+  }
+
+  return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
+                                                             aTag);
+}
+
 } // namespace
 
 const JSClass IDBObjectStore::sDummyPropJSClass = {
   "IDBObjectStore Dummy",
   0 /* flags */
 };
 
 IDBObjectStore::IDBObjectStore(IDBTransaction* aTransaction,
@@ -1319,17 +1514,17 @@ IDBObjectStore::AssertIsOnOwningThread()
   MOZ_ASSERT(mTransaction);
   mTransaction->AssertIsOnOwningThread();
 }
 
 #endif // DEBUG
 
 nsresult
 IDBObjectStore::GetAddInfo(JSContext* aCx,
-                           JS::Handle<JS::Value> aValue,
+                           ValueWrapper& aValueWrapper,
                            JS::Handle<JS::Value> aKeyVal,
                            StructuredCloneWriteInfo& aCloneWriteInfo,
                            Key& aKey,
                            nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
 {
   // Return DATA_ERR if a key was passed in and this objectStore uses inline
   // keys.
   if (!aKeyVal.isUndefined() && HasValidKeyPath()) {
@@ -1342,66 +1537,88 @@ IDBObjectStore::GetAddInfo(JSContext* aC
 
   if (!HasValidKeyPath()) {
     // Out-of-line keys must be passed in.
     rv = aKey.SetFromJSVal(aCx, aKeyVal);
     if (NS_FAILED(rv)) {
       return rv;
     }
   } else if (!isAutoIncrement) {
-    rv = GetKeyPath().ExtractKey(aCx, aValue, aKey);
+    if (!aValueWrapper.Clone(aCx)) {
+      return NS_ERROR_DOM_DATA_CLONE_ERR;
+    }
+
+    rv = GetKeyPath().ExtractKey(aCx, aValueWrapper.Value(), aKey);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   // Return DATA_ERR if no key was specified this isn't an autoIncrement
   // objectStore.
   if (aKey.IsUnset() && !isAutoIncrement) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
 
   // Figure out indexes and the index values to update here.
   const nsTArray<IndexMetadata>& indexes = mSpec->indexes();
 
-  const uint32_t idxCount = indexes.Length();
+  uint32_t idxCount = indexes.Length();
+
+  if (idxCount) {
+    if (!aValueWrapper.Clone(aCx)) {
+      return NS_ERROR_DOM_DATA_CLONE_ERR;
+    }
+
+    // Update idxCount, the structured clone process may trigger content code
+    // via getters/other which can potentially call CreateIndex/DeleteIndex.
+    idxCount = indexes.Length();
+  }
+
   aUpdateInfoArray.SetCapacity(idxCount); // Pretty good estimate
 
   for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) {
     const IndexMetadata& metadata = indexes[idxIndex];
 
     rv = AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(),
                                metadata.unique(), metadata.multiEntry(),
-                               metadata.locale(), aCx, aValue,
+                               metadata.locale(), aCx, aValueWrapper.Value(),
                                aUpdateInfoArray);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
-  GetAddInfoClosure data(aCloneWriteInfo, aValue);
 
   if (isAutoIncrement && HasValidKeyPath()) {
+    if (!aValueWrapper.Clone(aCx)) {
+      return NS_ERROR_DOM_DATA_CLONE_ERR;
+    }
+
+    GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
+
     MOZ_ASSERT(aKey.IsUnset());
 
     rv = GetKeyPath().ExtractOrCreateKey(aCx,
-                                         aValue,
+                                         aValueWrapper.Value(),
                                          aKey,
                                          &GetAddInfoCallback,
                                          &data);
   } else {
+    GetAddInfoClosure data(aCloneWriteInfo, aValueWrapper.Value());
+
     rv = GetAddInfoCallback(aCx, &data);
   }
 
   return rv;
 }
 
 already_AddRefed<IDBRequest>
 IDBObjectStore::AddOrPut(JSContext* aCx,
-                         JS::Handle<JS::Value> aValue,
+                         ValueWrapper& aValueWrapper,
                          JS::Handle<JS::Value> aKey,
                          bool aOverwrite,
                          bool aFromCursor,
                          ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aCx);
   MOZ_ASSERT_IF(aFromCursor, aOverwrite);
@@ -1417,22 +1634,21 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
     return nullptr;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     return nullptr;
   }
 
-  JS::Rooted<JS::Value> value(aCx, aValue);
   Key key;
   StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
   nsTArray<IndexUpdateInfo> updateInfo;
 
-  aRv = GetAddInfo(aCx, value, aKey, cloneWriteInfo, key, updateInfo);
+  aRv = GetAddInfo(aCx, aValueWrapper, aKey, cloneWriteInfo, key, updateInfo);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // Check the size limit of the serialized message which mainly consists of
   // a StructuredCloneBuffer, an encoded object key, and the encoded index keys.
   // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation
   // because the precise calculation would slow down this AddOrPut operation.
@@ -2494,10 +2710,45 @@ bool
 IDBObjectStore::HasValidKeyPath() const
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mSpec);
 
   return GetKeyPath().IsValid();
 }
 
+bool
+IDBObjectStore::
+ValueWrapper::Clone(JSContext* aCx)
+{
+  if (mCloned) {
+    return true;
+  }
+
+  static const JSStructuredCloneCallbacks callbacks = {
+    CopyingStructuredCloneReadCallback /* read */,
+    CopyingStructuredCloneWriteCallback /* write */,
+    nullptr /* reportError */,
+    nullptr /* readTransfer */,
+    nullptr /* writeTransfer */,
+    nullptr /* freeTransfer */,
+  };
+
+  StructuredCloneInfo cloneInfo;
+
+  JS::Rooted<JS::Value> clonedValue(aCx);
+  if (!JS_StructuredClone(aCx,
+                          mValue,
+                          &clonedValue,
+                          &callbacks,
+                          &cloneInfo)) {
+    return false;
+  }
+
+  mValue = clonedValue;
+
+  mCloned = true;
+
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -69,16 +69,45 @@ class IDBObjectStore final
   nsTArray<RefPtr<IDBIndex>> mIndexes;
   nsTArray<RefPtr<IDBIndex>> mDeletedIndexes;
 
   const int64_t mId;
   bool mRooted;
 
 public:
   struct StructuredCloneWriteInfo;
+  struct StructuredCloneInfo;
+
+  class MOZ_STACK_CLASS ValueWrapper final
+  {
+    JS::Rooted<JS::Value> mValue;
+    bool mCloned;
+
+  public:
+    ValueWrapper(JSContext* aCx, JS::Handle<JS::Value> aValue)
+      : mValue(aCx, aValue)
+      , mCloned(false)
+    {
+      MOZ_COUNT_CTOR(IDBObjectStore::ValueWrapper);
+    }
+
+    ~ValueWrapper()
+    {
+      MOZ_COUNT_DTOR(IDBObjectStore::ValueWrapper);
+    }
+
+    const JS::Rooted<JS::Value>&
+    Value() const
+    {
+      return mValue;
+    }
+
+    bool
+    Clone(JSContext* aCx);
+  };
 
   static already_AddRefed<IDBObjectStore>
   Create(IDBTransaction* aTransaction, const ObjectStoreSpec& aSpec);
 
   static nsresult
   AppendIndexUpdateInfo(int64_t aIndexID,
                         const KeyPath& aKeyPath,
                         bool aUnique,
@@ -174,28 +203,34 @@ public:
   already_AddRefed<IDBRequest>
   Add(JSContext* aCx,
       JS::Handle<JS::Value> aValue,
       JS::Handle<JS::Value> aKey,
       ErrorResult& aRv)
   {
     AssertIsOnOwningThread();
 
-    return AddOrPut(aCx, aValue, aKey, false, /* aFromCursor */ false, aRv);
+    ValueWrapper valueWrapper(aCx, aValue);
+
+    return AddOrPut(aCx, valueWrapper, aKey, false, /* aFromCursor */ false,
+                    aRv);
   }
 
   already_AddRefed<IDBRequest>
   Put(JSContext* aCx,
       JS::Handle<JS::Value> aValue,
       JS::Handle<JS::Value> aKey,
       ErrorResult& aRv)
   {
     AssertIsOnOwningThread();
 
-    return AddOrPut(aCx, aValue, aKey, true, /* aFromCursor */ false, aRv);
+    ValueWrapper valueWrapper(aCx, aValue);
+
+    return AddOrPut(aCx, valueWrapper, aKey, true, /* aFromCursor */ false,
+                    aRv);
   }
 
   already_AddRefed<IDBRequest>
   Delete(JSContext* aCx,
          JS::Handle<JS::Value> aKey,
          ErrorResult& aRv)
   {
     AssertIsOnOwningThread();
@@ -326,25 +361,25 @@ public:
 
 private:
   IDBObjectStore(IDBTransaction* aTransaction, const ObjectStoreSpec* aSpec);
 
   ~IDBObjectStore();
 
   nsresult
   GetAddInfo(JSContext* aCx,
-             JS::Handle<JS::Value> aValue,
+             ValueWrapper& aValueWrapper,
              JS::Handle<JS::Value> aKeyVal,
              StructuredCloneWriteInfo& aCloneWriteInfo,
              Key& aKey,
              nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
 
   already_AddRefed<IDBRequest>
   AddOrPut(JSContext* aCx,
-           JS::Handle<JS::Value> aValue,
+           ValueWrapper& aValueWrapper,
            JS::Handle<JS::Value> aKey,
            bool aOverwrite,
            bool aFromCursor,
            ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
   DeleteInternal(JSContext* aCx,
                  JS::Handle<JS::Value> aKey,