tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SharedMap.cpp (13832B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SharedMap.h"
      8 
      9 #include "MemMapSnapshot.h"
     10 #include "ScriptPreloader-inl.h"
     11 #include "SharedMapChangeEvent.h"
     12 #include "mozilla/IOBuffers.h"
     13 #include "mozilla/RefPtr.h"
     14 #include "mozilla/ScriptPreloader.h"
     15 #include "mozilla/Try.h"
     16 #include "mozilla/dom/AutoEntryScript.h"
     17 #include "mozilla/dom/BlobImpl.h"
     18 #include "mozilla/dom/ContentParent.h"
     19 #include "mozilla/dom/ContentProcessMessageManager.h"
     20 #include "mozilla/dom/IPCBlobUtils.h"
     21 #include "mozilla/dom/RootedDictionary.h"
     22 #include "mozilla/dom/ScriptSettings.h"
     23 
     24 using namespace mozilla::loader;
     25 
     26 namespace mozilla {
     27 
     28 using namespace ipc;
     29 
     30 namespace dom::ipc {
     31 
     32 // Align to size of uintptr_t here, to be safe. It's probably not strictly
     33 // necessary, though.
     34 constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t);
     35 
     36 static inline void AlignTo(size_t* aOffset, size_t aAlign) {
     37  if (auto mod = *aOffset % aAlign) {
     38    *aOffset += aAlign - mod;
     39  }
     40 }
     41 
     42 SharedMap::SharedMap() = default;
     43 
     44 SharedMap::SharedMap(nsIGlobalObject* aGlobal, SharedMemoryHandle&& aMapHandle,
     45                     nsTArray<RefPtr<BlobImpl>>&& aBlobs)
     46    : DOMEventTargetHelper(aGlobal),
     47      mBlobImpls(std::move(aBlobs)),
     48      mHandle(std::move(aMapHandle)) {}
     49 
     50 bool SharedMap::Has(const nsACString& aName) {
     51  (void)MaybeRebuild();
     52  return mEntries.Contains(aName);
     53 }
     54 
     55 void SharedMap::Get(JSContext* aCx, const nsACString& aName,
     56                    JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) {
     57  auto res = MaybeRebuild();
     58  if (res.isErr()) {
     59    aRv.Throw(res.unwrapErr());
     60    return;
     61  }
     62 
     63  Entry* entry = mEntries.Get(aName);
     64  if (!entry) {
     65    aRetVal.setNull();
     66    return;
     67  }
     68 
     69  entry->Read(aCx, aRetVal, aRv);
     70 }
     71 
     72 void SharedMap::Entry::Read(JSContext* aCx,
     73                            JS::MutableHandle<JS::Value> aRetVal,
     74                            ErrorResult& aRv) {
     75  if (mData.is<UniquePtr<StructuredCloneData>>()) {
     76    // We have a temporary buffer for a key that was changed after the last
     77    // snapshot. Just decode it directly.
     78    auto& holder = mData.as<UniquePtr<StructuredCloneData>>();
     79    holder->Read(aCx, aRetVal, aRv);
     80    return;
     81  }
     82 
     83  // We have a pointer to a shared memory region containing our structured
     84  // clone data. Create a temporary buffer to decode that data, and then
     85  // discard it so that we don't keep a separate process-local copy around any
     86  // longer than necessary.
     87  StructuredCloneData holder;
     88  if (!holder.CopyExternalData(Data(), Size())) {
     89    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     90    return;
     91  }
     92  if (mBlobCount) {
     93    holder.BlobImpls().AppendElements(Blobs());
     94  }
     95  holder.Read(aCx, aRetVal, aRv);
     96 }
     97 
     98 void SharedMap::Update(SharedMemoryHandle&& aMapHandle,
     99                       nsTArray<RefPtr<BlobImpl>>&& aBlobs,
    100                       nsTArray<nsCString>&& aChangedKeys) {
    101  MOZ_DIAGNOSTIC_ASSERT(!mWritable);
    102 
    103  mMapping = nullptr;
    104  mHandle = std::move(aMapHandle);
    105  mEntries.Clear();
    106  mEntryArray.reset();
    107 
    108  mBlobImpls = std::move(aBlobs);
    109 
    110  AutoEntryScript aes(GetParentObject(), "SharedMap change event");
    111  JSContext* cx = aes.cx();
    112 
    113  RootedDictionary<MozSharedMapChangeEventInit> init(cx);
    114  if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) {
    115    NS_WARNING("Failed to dispatch SharedMap change event");
    116    return;
    117  }
    118  for (auto& key : aChangedKeys) {
    119    (void)init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key), fallible);
    120  }
    121 
    122  RefPtr<SharedMapChangeEvent> event =
    123      SharedMapChangeEvent::Constructor(this, u"change"_ns, init);
    124  event->SetTrusted(true);
    125 
    126  DispatchEvent(*event);
    127 }
    128 
    129 const nsTArray<SharedMap::Entry*>& SharedMap::EntryArray() const {
    130  if (mEntryArray.isNothing()) {
    131    MaybeRebuild();
    132 
    133    mEntryArray.emplace(mEntries.Count());
    134    auto& array = mEntryArray.ref();
    135    for (auto& entry : mEntries) {
    136      array.AppendElement(entry.GetWeak());
    137    }
    138  }
    139 
    140  return mEntryArray.ref();
    141 }
    142 
    143 const nsString SharedMap::GetKeyAtIndex(uint32_t aIndex) const {
    144  return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name());
    145 }
    146 
    147 bool SharedMap::GetValueAtIndex(JSContext* aCx, uint32_t aIndex,
    148                                JS::MutableHandle<JS::Value> aResult) const {
    149  ErrorResult rv;
    150  EntryArray()[aIndex]->Read(aCx, aResult, rv);
    151  if (rv.MaybeSetPendingException(aCx)) {
    152    return false;
    153  }
    154  return true;
    155 }
    156 
    157 void SharedMap::Entry::TakeData(UniquePtr<StructuredCloneData> aHolder) {
    158  mData = AsVariant(std::move(aHolder));
    159 
    160  mSize = Holder().Data().Size();
    161  mBlobCount = Holder().BlobImpls().Length();
    162 }
    163 
    164 void SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset,
    165                                   uint16_t aNewBlobOffset) {
    166  if (mData.is<UniquePtr<StructuredCloneData>>()) {
    167    char* ptr = aDestPtr;
    168    Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
    169      memcpy(ptr, aData, aSize);
    170      ptr += aSize;
    171      return true;
    172    });
    173    MOZ_ASSERT(uint32_t(ptr - aDestPtr) == mSize);
    174  } else {
    175    memcpy(aDestPtr, Data(), mSize);
    176  }
    177 
    178  mData = AsVariant(aNewOffset);
    179  mBlobOffset = aNewBlobOffset;
    180 }
    181 
    182 Result<Ok, nsresult> SharedMap::MaybeRebuild() {
    183  if (mMapping || !mHandle) {
    184    return Ok();
    185  }
    186 
    187  MOZ_DIAGNOSTIC_ASSERT(!mWritable);
    188 
    189  // This function maps a shared memory region created by Serialize() and reads
    190  // its header block to build a new mEntries hashtable of its contents.
    191  //
    192  // The entries created by this function contain a pointer to this SharedMap
    193  // instance, and the offsets and sizes of their structured clone data within
    194  // its shared memory region. When needed, that structured clone data is
    195  // retrieved directly as indexes into the SharedMap's shared memory region.
    196 
    197  mMapping = mHandle.Map();
    198  if (!mMapping) {
    199    return Err(NS_ERROR_FAILURE);
    200  }
    201  mHandle = nullptr;
    202 
    203  Range<const uint8_t> inputRange(mMapping.DataAsSpan<uint8_t>());
    204  InputBuffer buffer(inputRange);
    205 
    206  uint32_t count;
    207  buffer.codeUint32(count);
    208 
    209  MOZ_ASSERT(mEntries.IsEmpty());
    210  MOZ_ASSERT(mEntryArray.isNothing());
    211  for (uint32_t i = 0; i < count; i++) {
    212    auto entry = MakeUnique<Entry>(*this);
    213    entry->Code(buffer);
    214 
    215    // This buffer was created at runtime, during this session, so any errors
    216    // indicate memory corruption, and are fatal.
    217    MOZ_RELEASE_ASSERT(!buffer.error());
    218 
    219    // Note: While the order of evaluation of the arguments to Put doesn't
    220    // matter for this (the actual move will only happen within Put), to be
    221    // clear about this, we call entry->Name() before calling Put.
    222    const auto& name = entry->Name();
    223    mEntries.InsertOrUpdate(name, std::move(entry));
    224  }
    225 
    226  return Ok();
    227 }
    228 
    229 void SharedMap::MaybeRebuild() const {
    230  (void)const_cast<SharedMap*>(this)->MaybeRebuild();
    231 }
    232 
    233 WritableSharedMap::WritableSharedMap() {
    234  mWritable = true;
    235  // Serialize the initial empty contents of the map immediately so that we
    236  // always have a file descriptor to send.
    237  (void)Serialize();
    238  MOZ_RELEASE_ASSERT(mHandle.IsValid() && mMapping.IsValid());
    239 }
    240 
    241 SharedMap* WritableSharedMap::GetReadOnly() {
    242  if (!mReadOnly) {
    243    nsTArray<RefPtr<BlobImpl>> blobs(mBlobImpls.Clone());
    244    mReadOnly =
    245        new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(),
    246                      mHandle.Clone(), std::move(blobs));
    247  }
    248  return mReadOnly;
    249 }
    250 
    251 Result<Ok, nsresult> WritableSharedMap::Serialize() {
    252  // Serializes a new snapshot of the map, initializes a new read-only shared
    253  // memory region with its contents, and updates all entries to point to that
    254  // new snapshot.
    255  //
    256  // The layout of the snapshot is as follows:
    257  //
    258  // - A header containing a uint32 count field containing the number of
    259  //   entries in the map, followed by that number of serialized entry headers,
    260  //   as produced by Entry::Code.
    261  //
    262  // - A data block containing structured clone data for each of the entries'
    263  //   values. This data is referenced by absolute byte offsets from the start
    264  //   of the shared memory region, encoded in each of the entry header values.
    265  //   Each entry's data is aligned to kStructuredCloneAlign, and therefore may
    266  //   have alignment padding before it.
    267  //
    268  // This serialization format is decoded by the MaybeRebuild() method of
    269  // read-only SharedMap() instances, and used to populate their mEntries
    270  // hashtables.
    271  //
    272  // Writable instances never read the header blocks, but instead directly
    273  // update their Entry instances to point to the appropriate offsets in the
    274  // shared memory region created by this function.
    275 
    276  uint32_t count = mEntries.Count();
    277 
    278  size_t dataSize = 0;
    279  size_t headerSize = sizeof(count);
    280  size_t blobCount = 0;
    281 
    282  for (const auto& entry : mEntries.Values()) {
    283    headerSize += entry->HeaderSize();
    284    blobCount += entry->BlobCount();
    285 
    286    dataSize += entry->Size();
    287    AlignTo(&dataSize, kStructuredCloneAlign);
    288  }
    289 
    290  size_t offset = headerSize;
    291  AlignTo(&offset, kStructuredCloneAlign);
    292 
    293  OutputBuffer header;
    294  header.codeUint32(count);
    295 
    296  MemMapSnapshot mem;
    297  MOZ_TRY(mem.Init(offset + dataSize));
    298 
    299  auto ptr = mem.Get<char>();
    300 
    301  // We need to build the new array of blobs before we overwrite the existing
    302  // one, since previously-serialized entries will store their blob references
    303  // as indexes into our blobs array.
    304  nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount);
    305 
    306  for (const auto& entry : mEntries.Values()) {
    307    AlignTo(&offset, kStructuredCloneAlign);
    308 
    309    size_t blobOffset = blobImpls.Length();
    310    if (entry->BlobCount()) {
    311      blobImpls.AppendElements(entry->Blobs());
    312    }
    313 
    314    entry->ExtractData(&ptr[offset], offset, blobOffset);
    315    entry->Code(header);
    316 
    317    offset += entry->Size();
    318  }
    319 
    320  mBlobImpls = std::move(blobImpls);
    321 
    322  // FIXME: We should create a separate OutputBuffer class which can encode to
    323  // a static memory region rather than dynamically allocating and then
    324  // copying.
    325  MOZ_ASSERT(header.cursor() == headerSize);
    326  memcpy(ptr.get(), header.Get(), header.cursor());
    327 
    328  // We've already updated offsets at this point. We need this to succeed.
    329  auto result = mem.Finalize();
    330  MOZ_RELEASE_ASSERT(result.isOk());
    331  mHandle = result.unwrap();
    332  mMapping = mHandle.Map();
    333  MOZ_RELEASE_ASSERT(mMapping.IsValid());
    334 
    335  return Ok();
    336 }
    337 
    338 void WritableSharedMap::SendTo(ContentParent* aParent) const {
    339  nsTArray<IPCBlob> blobs(mBlobImpls.Length());
    340 
    341  for (auto& blobImpl : mBlobImpls) {
    342    nsresult rv = IPCBlobUtils::Serialize(blobImpl, *blobs.AppendElement());
    343    if (NS_WARN_IF(NS_FAILED(rv))) {
    344      continue;
    345    }
    346  }
    347 
    348  (void)aParent->SendUpdateSharedData(mHandle.Clone(), blobs, mChangedKeys);
    349 }
    350 
    351 void WritableSharedMap::BroadcastChanges() {
    352  if (mChangedKeys.IsEmpty()) {
    353    return;
    354  }
    355 
    356  if (!Serialize().isOk()) {
    357    return;
    358  }
    359 
    360  nsTArray<ContentParent*> parents;
    361  ContentParent::GetAll(parents);
    362  for (auto& parent : parents) {
    363    SendTo(parent);
    364  }
    365 
    366  if (mReadOnly) {
    367    nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls.Clone());
    368    mReadOnly->Update(mHandle.Clone(), std::move(blobImpls),
    369                      std::move(mChangedKeys));
    370  }
    371 
    372  mChangedKeys.Clear();
    373 }
    374 
    375 void WritableSharedMap::Delete(const nsACString& aName) {
    376  if (mEntries.Remove(aName)) {
    377    KeyChanged(aName);
    378  }
    379 }
    380 
    381 void WritableSharedMap::Set(JSContext* aCx, const nsACString& aName,
    382                            JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
    383  auto holder = MakeUnique<StructuredCloneData>();
    384 
    385  holder->Write(aCx, aValue, aRv);
    386  if (aRv.Failed()) {
    387    return;
    388  }
    389 
    390  if (!holder->InputStreams().IsEmpty()) {
    391    aRv.Throw(NS_ERROR_INVALID_ARG);
    392    return;
    393  }
    394 
    395  Entry* entry = mEntries.GetOrInsertNew(aName, *this, aName);
    396  entry->TakeData(std::move(holder));
    397 
    398  KeyChanged(aName);
    399 }
    400 
    401 void WritableSharedMap::Flush() { BroadcastChanges(); }
    402 
    403 void WritableSharedMap::IdleFlush() {
    404  mPendingFlush = false;
    405  Flush();
    406 }
    407 
    408 nsresult WritableSharedMap::KeyChanged(const nsACString& aName) {
    409  if (!mChangedKeys.ContainsSorted(aName)) {
    410    mChangedKeys.InsertElementSorted(aName);
    411  }
    412  mEntryArray.reset();
    413 
    414  if (!mPendingFlush) {
    415    MOZ_TRY(NS_DispatchToCurrentThreadQueue(
    416        NewRunnableMethod("WritableSharedMap::IdleFlush", this,
    417                          &WritableSharedMap::IdleFlush),
    418        EventQueuePriority::Idle));
    419    mPendingFlush = true;
    420  }
    421  return NS_OK;
    422 }
    423 
    424 JSObject* SharedMap::WrapObject(JSContext* aCx,
    425                                JS::Handle<JSObject*> aGivenProto) {
    426  return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
    427 }
    428 
    429 JSObject* WritableSharedMap::WrapObject(JSContext* aCx,
    430                                        JS::Handle<JSObject*> aGivenProto) {
    431  return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto);
    432 }
    433 
    434 /* static */
    435 already_AddRefed<SharedMapChangeEvent> SharedMapChangeEvent::Constructor(
    436    EventTarget* aEventTarget, const nsAString& aType,
    437    const MozSharedMapChangeEventInit& aInit) {
    438  RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget);
    439 
    440  bool trusted = event->Init(aEventTarget);
    441  event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable);
    442  event->SetTrusted(trusted);
    443  event->SetComposed(aInit.mComposed);
    444 
    445  event->mChangedKeys = aInit.mChangedKeys;
    446 
    447  return event.forget();
    448 }
    449 
    450 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableSharedMap, SharedMap, mReadOnly)
    451 
    452 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableSharedMap)
    453 NS_INTERFACE_MAP_END_INHERITING(SharedMap)
    454 
    455 NS_IMPL_ADDREF_INHERITED(WritableSharedMap, SharedMap)
    456 NS_IMPL_RELEASE_INHERITED(WritableSharedMap, SharedMap)
    457 
    458 }  // namespace dom::ipc
    459 }  // namespace mozilla