dom/ipc/SharedStringMap.cpp
author Jed Davis <jld@mozilla.com>
Wed, 14 Aug 2019 22:48:38 +0000
changeset 488049 1fa598bf26998154cfc2c189a2a5f7fb862c7640
parent 480592 56834c5fc9390da3bdfb41a3fe3f06119fbb3502
child 555760 c0adbf7522dc9daa4f17a7607c5eb45a99d08d44
permissions -rw-r--r--
Bug 1479960 - Give SharedStringMap a magic number so that all zeroes isn't a valid instance. r=kmag There was a strange bug when converting SharedStringMap to use shared memory: on Android, some tests would fail because a pref wasn't set or there was something wrong with an expected error message. The root cause was that mapping ashmem with MAP_PRIVATE results in all zeroes (see bug 1525803), but that was read as a valid SharedStringMap with length 0. To prevent any possible future issues like that, this patch adds a nonzero magic number to the header. It fits into padding on 64-bit and the cost of setting and checking it should be essentially free. Depends on D26744 Differential Revision: https://phabricator.services.mozilla.com/D26745

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SharedStringMap.h"

#include "MemMapSnapshot.h"
#include "ScriptPreloader-inl.h"

#include "mozilla/BinarySearch.h"
#include "mozilla/ipc/FileDescriptor.h"

using namespace mozilla::loader;

namespace mozilla {

using namespace ipc;

namespace dom {
namespace ipc {

static constexpr uint32_t kSharedStringMapMagic = 0x9e3779b9;

static inline size_t GetAlignmentOffset(size_t aOffset, size_t aAlign) {
  auto mod = aOffset % aAlign;
  return mod ? aAlign - mod : 0;
}

SharedStringMap::SharedStringMap(const FileDescriptor& aMapFile,
                                 size_t aMapSize) {
  auto result = mMap.initWithHandle(aMapFile, aMapSize);
  MOZ_RELEASE_ASSERT(result.isOk());
  MOZ_RELEASE_ASSERT(GetHeader().mMagic == kSharedStringMapMagic);
  // We return literal nsStrings and nsCStrings pointing to the mapped data,
  // which means that we may still have references to the mapped data even
  // after this instance is destroyed. That means that we need to keep the
  // mapping alive until process shutdown, in order to be safe.
  mMap.setPersistent();
}

SharedStringMap::SharedStringMap(SharedStringMapBuilder&& aBuilder) {
  auto result = aBuilder.Finalize(mMap);
  MOZ_RELEASE_ASSERT(result.isOk());
  MOZ_RELEASE_ASSERT(GetHeader().mMagic == kSharedStringMapMagic);
  mMap.setPersistent();
}

mozilla::ipc::FileDescriptor SharedStringMap::CloneFileDescriptor() const {
  return mMap.cloneHandle();
}

bool SharedStringMap::Has(const nsCString& aKey) {
  size_t index;
  return Find(aKey, &index);
}

bool SharedStringMap::Get(const nsCString& aKey, nsAString& aValue) {
  const auto& entries = Entries();

  size_t index;
  if (!Find(aKey, &index)) {
    return false;
  }

  aValue.Assign(ValueTable().Get(entries[index].mValue));
  return true;
}

bool SharedStringMap::Find(const nsCString& aKey, size_t* aIndex) {
  const auto& keys = KeyTable();

  return BinarySearchIf(
      Entries(), 0, EntryCount(),
      [&](const Entry& aEntry) {
        return aKey.Compare(keys.GetBare(aEntry.mKey));
      },
      aIndex);
}

void SharedStringMapBuilder::Add(const nsCString& aKey,
                                 const nsString& aValue) {
  mEntries.Put(aKey, {mKeyTable.Add(aKey), mValueTable.Add(aValue)});
}

Result<Ok, nsresult> SharedStringMapBuilder::Finalize(
    loader::AutoMemMap& aMap) {
  using Header = SharedStringMap::Header;

  MOZ_ASSERT(mEntries.Count() == mKeyTable.Count());

  nsTArray<nsCString> keys(mEntries.Count());
  for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
    keys.AppendElement(iter.Key());
  }
  keys.Sort();

  Header header = {kSharedStringMapMagic, uint32_t(keys.Length())};

  size_t offset = sizeof(header);
  offset += GetAlignmentOffset(offset, alignof(Header));

  offset += keys.Length() * sizeof(SharedStringMap::Entry);

  header.mKeyStringsOffset = offset;
  header.mKeyStringsSize = mKeyTable.Size();

  offset += header.mKeyStringsSize;
  offset +=
      GetAlignmentOffset(offset, alignof(decltype(mValueTable)::ElemType));

  header.mValueStringsOffset = offset;
  header.mValueStringsSize = mValueTable.Size();

  offset += header.mValueStringsSize;

  MemMapSnapshot mem;
  MOZ_TRY(mem.Init(offset));

  auto headerPtr = mem.Get<Header>();
  headerPtr[0] = header;

  auto* entry = reinterpret_cast<Entry*>(&headerPtr[1]);
  for (auto& key : keys) {
    *entry++ = mEntries.Get(key);
  }

  auto ptr = mem.Get<uint8_t>();

  mKeyTable.Write({&ptr[header.mKeyStringsOffset], header.mKeyStringsSize});

  mValueTable.Write(
      {&ptr[header.mValueStringsOffset], header.mValueStringsSize});

  mKeyTable.Clear();
  mValueTable.Clear();
  mEntries.Clear();

  return mem.Finalize(aMap);
}

}  // namespace ipc
}  // namespace dom
}  // namespace mozilla