/* -*- 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/. */

/*
 * JS atom table.
 */

#include "vm/JSAtom-inl.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/RangedPtr.h"
#include "mozilla/Unused.h"

#include <string.h>

#include "jstypes.h"

#include "gc/GC.h"
#include "gc/Marking.h"
#include "gc/MaybeRooted.h"
#include "js/CharacterEncoding.h"
#include "js/Symbol.h"
#include "util/Text.h"
#include "vm/JSContext.h"
#include "vm/SymbolType.h"
#include "vm/Xdr.h"

#include "gc/AtomMarking-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/Realm-inl.h"
#include "vm/StringType-inl.h"

using namespace js;

using mozilla::ArrayEnd;
using mozilla::ArrayLength;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::RangedPtr;

template <typename CharT, typename InputCharsT>
extern void InflateUTF8CharsToBufferAndTerminate(const InputCharsT src,
                                                 CharT* dst, size_t dstLen,
                                                 JS::SmallestEncoding encoding);

template <typename CharT, typename CharsT>
extern bool UTF8OrWTF8EqualsChars(const CharsT utf8, const CharT* chars);

template <typename InputCharsT>
extern bool GetUTF8AtomizationData(JSContext* cx, const InputCharsT utf8,
                                   size_t* outlen,
                                   JS::SmallestEncoding* encoding,
                                   HashNumber* hashNum);

struct js::AtomHasher::Lookup {
  union {
    const JS::Latin1Char* latin1Chars;
    const char16_t* twoByteChars;
    LittleEndianChars littleEndianChars;
    const char* utf8Bytes;
  };
  enum { TwoByteChar, LittleEndianTwoByte, Latin1, UTF8, WTF8 } type;
  size_t length;
  size_t byteLength;
  const JSAtom* atom; /* Optional. */
  JS::AutoCheckCannotGC nogc;

  HashNumber hash;

  MOZ_ALWAYS_INLINE Lookup(const char* utf8Bytes, size_t byteLen, size_t length,
                           HashNumber hash)
      : utf8Bytes(utf8Bytes),
        type(UTF8),
        length(length),
        byteLength(byteLen),
        atom(nullptr),
        hash(hash) {}

  MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length)
      : twoByteChars(chars),
        type(TwoByteChar),
        length(length),
        atom(nullptr),
        hash(mozilla::HashString(chars, length)) {}

  MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length)
      : latin1Chars(chars),
        type(Latin1),
        length(length),
        atom(nullptr),
        hash(mozilla::HashString(chars, length)) {}

  inline explicit Lookup(const JSAtom* atom)
      : type(atom->hasLatin1Chars() ? Latin1 : TwoByteChar),
        length(atom->length()),
        atom(atom),
        hash(atom->hash()) {
    if (type == Latin1) {
      latin1Chars = atom->latin1Chars(nogc);
      MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash);
    } else {
      MOZ_ASSERT(type == TwoByteChar);
      twoByteChars = atom->twoByteChars(nogc);
      MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash);
    }
  }

  MOZ_ALWAYS_INLINE Lookup(LittleEndianChars chars, size_t length)
      : littleEndianChars(chars),
        type(LittleEndianTwoByte),
        length(length),
        atom(nullptr),
        hash(mozilla::HashStringKnownLength(chars, length)) {}
};

inline HashNumber js::AtomHasher::hash(const Lookup& l) { return l.hash; }

MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const AtomStateEntry& entry,
                                             const Lookup& lookup) {
  JSAtom* key = entry.asPtrUnbarriered();
  if (lookup.atom) {
    return lookup.atom == key;
  }
  if (key->length() != lookup.length || key->hash() != lookup.hash) {
    return false;
  }

  auto EqualsLittleEndianChars = [&lookup](auto keyChars) {
    for (size_t i = 0, len = lookup.length; i < len; i++) {
      if (keyChars[i] != lookup.littleEndianChars[i]) {
        return false;
      }
    }
    return true;
  };

  if (key->hasLatin1Chars()) {
    const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
    switch (lookup.type) {
      case Lookup::Latin1:
        return mozilla::ArrayEqual(keyChars, lookup.latin1Chars, lookup.length);
      case Lookup::TwoByteChar:
        return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
      case Lookup::LittleEndianTwoByte:
        return EqualsLittleEndianChars(keyChars);
      case Lookup::UTF8: {
        JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength);
        return UTF8OrWTF8EqualsChars(utf8, keyChars);
      }
      case Lookup::WTF8: {
        JS::WTF8Chars wtf8(lookup.utf8Bytes, lookup.byteLength);
        return UTF8OrWTF8EqualsChars(wtf8, keyChars);
      }
    }
  }

  const char16_t* keyChars = key->twoByteChars(lookup.nogc);
  switch (lookup.type) {
    case Lookup::Latin1:
      return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
    case Lookup::TwoByteChar:
      return mozilla::ArrayEqual(keyChars, lookup.twoByteChars, lookup.length);
    case Lookup::LittleEndianTwoByte:
      return EqualsLittleEndianChars(keyChars);
    case Lookup::UTF8: {
      JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength);
      return UTF8OrWTF8EqualsChars(utf8, keyChars);
    }
    case Lookup::WTF8: {
      JS::WTF8Chars wtf8(lookup.utf8Bytes, lookup.byteLength);
      return UTF8OrWTF8EqualsChars(wtf8, keyChars);
    }
  }

  MOZ_ASSERT_UNREACHABLE("AtomHasher::match unknown type");
  return false;
}

inline JSAtom* js::AtomStateEntry::asPtr(JSContext* cx) const {
  JSAtom* atom = asPtrUnbarriered();
  if (!cx->isHelperThreadContext()) {
    JSString::readBarrier(atom);
  }
  return atom;
}

UniqueChars js::AtomToPrintableString(JSContext* cx, JSAtom* atom) {
  return QuoteString(cx, atom);
}

#define DEFINE_PROTO_STRING(name, clasp) const char js_##name##_str[] = #name;
JS_FOR_EACH_PROTOTYPE(DEFINE_PROTO_STRING)
#undef DEFINE_PROTO_STRING

#define CONST_CHAR_STR(idpart, id, text) const char js_##idpart##_str[] = text;
FOR_EACH_COMMON_PROPERTYNAME(CONST_CHAR_STR)
#undef CONST_CHAR_STR

// Use a low initial capacity for the permanent atoms table to avoid penalizing
// runtimes that create a small number of atoms.
static const uint32_t JS_PERMANENT_ATOM_SIZE = 64;

MOZ_ALWAYS_INLINE AtomSet::Ptr js::FrozenAtomSet::readonlyThreadsafeLookup(
    const AtomSet::Lookup& l) const {
  return mSet->readonlyThreadsafeLookup(l);
}

struct CommonNameInfo {
  const char* str;
  size_t length;
};

bool JSRuntime::initializeAtoms(JSContext* cx) {
  MOZ_ASSERT(!atoms_);
  MOZ_ASSERT(!permanentAtomsDuringInit_);
  MOZ_ASSERT(!permanentAtoms_);

  if (parentRuntime) {
    permanentAtoms_ = parentRuntime->permanentAtoms_;

    staticStrings = parentRuntime->staticStrings;
    commonNames = parentRuntime->commonNames;
    emptyString = parentRuntime->emptyString;
    wellKnownSymbols = parentRuntime->wellKnownSymbols;

    atoms_ = js_new<AtomsTable>();
    if (!atoms_) {
      return false;
    }

    return atoms_->init();
  }

  permanentAtomsDuringInit_ = js_new<AtomSet>(JS_PERMANENT_ATOM_SIZE);
  if (!permanentAtomsDuringInit_) {
    return false;
  }

  staticStrings = js_new<StaticStrings>();
  if (!staticStrings || !staticStrings->init(cx)) {
    return false;
  }

  static const CommonNameInfo cachedNames[] = {
#define COMMON_NAME_INFO(idpart, id, text) \
  {js_##idpart##_str, sizeof(text) - 1},
      FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INFO)
#undef COMMON_NAME_INFO
#define COMMON_NAME_INFO(name, clasp) {js_##name##_str, sizeof(#name) - 1},
          JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INFO)
#undef COMMON_NAME_INFO
#define COMMON_NAME_INFO(name) {#name, sizeof(#name) - 1},
              JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INFO)
#undef COMMON_NAME_INFO
#define COMMON_NAME_INFO(name) {"Symbol." #name, sizeof("Symbol." #name) - 1},
                  JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INFO)
#undef COMMON_NAME_INFO
  };

  commonNames = js_new<JSAtomState>();
  if (!commonNames) {
    return false;
  }

  ImmutablePropertyNamePtr* names =
      reinterpret_cast<ImmutablePropertyNamePtr*>(commonNames.ref());
  for (size_t i = 0; i < ArrayLength(cachedNames); i++, names++) {
    JSAtom* atom =
        Atomize(cx, cachedNames[i].str, cachedNames[i].length, PinAtom);
    if (!atom) {
      return false;
    }
    names->init(atom->asPropertyName());
  }
  MOZ_ASSERT(uintptr_t(names) == uintptr_t(commonNames + 1));

  emptyString = commonNames->empty;

  // Create the well-known symbols.
  auto wks = js_new<WellKnownSymbols>();
  if (!wks) {
    return false;
  }

  // Prevent GC until we have fully initialized the well known symbols table.
  // Faster than zeroing the array and null checking during every GC.
  gc::AutoSuppressGC nogc(cx);

  ImmutablePropertyNamePtr* descriptions =
      commonNames->wellKnownSymbolDescriptions();
  ImmutableSymbolPtr* symbols = reinterpret_cast<ImmutableSymbolPtr*>(wks);
  for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
    HandlePropertyName description = descriptions[i];
    JS::Symbol* symbol = JS::Symbol::new_(cx, JS::SymbolCode(i), description);
    if (!symbol) {
      ReportOutOfMemory(cx);
      return false;
    }
    symbols[i].init(symbol);
  }

  wellKnownSymbols = wks;
  return true;
}

void JSRuntime::finishAtoms() {
  js_delete(atoms_.ref());

  if (!parentRuntime) {
    js_delete(permanentAtomsDuringInit_.ref());
    js_delete(permanentAtoms_.ref());
    js_delete(staticStrings.ref());
    js_delete(commonNames.ref());
    js_delete(wellKnownSymbols.ref());
  }

  atoms_ = nullptr;
  permanentAtomsDuringInit_ = nullptr;
  permanentAtoms_ = nullptr;
  staticStrings = nullptr;
  commonNames = nullptr;
  wellKnownSymbols = nullptr;
  emptyString = nullptr;
}

class AtomsTable::AutoLock {
  Mutex* lock = nullptr;

 public:
  MOZ_ALWAYS_INLINE explicit AutoLock(JSRuntime* rt, Mutex& aLock) {
    if (rt->hasHelperThreadZones()) {
      lock = &aLock;
      lock->lock();
    }
  }

  MOZ_ALWAYS_INLINE ~AutoLock() {
    if (lock) {
      lock->unlock();
    }
  }
};

AtomsTable::Partition::Partition(uint32_t index)
    : lock(
          MutexId{mutexid::AtomsTable.name, mutexid::AtomsTable.order + index}),
      atoms(InitialTableSize),
      atomsAddedWhileSweeping(nullptr) {}

AtomsTable::Partition::~Partition() { MOZ_ASSERT(!atomsAddedWhileSweeping); }

AtomsTable::~AtomsTable() {
  for (size_t i = 0; i < PartitionCount; i++) {
    js_delete(partitions[i]);
  }
}

bool AtomsTable::init() {
  for (size_t i = 0; i < PartitionCount; i++) {
    partitions[i] = js_new<Partition>(i);
    if (!partitions[i]) {
      return false;
    }
  }
  return true;
}

void AtomsTable::lockAll() {
  MOZ_ASSERT(!allPartitionsLocked);

  for (size_t i = 0; i < PartitionCount; i++) {
    partitions[i]->lock.lock();
  }

#ifdef DEBUG
  allPartitionsLocked = true;
#endif
}

void AtomsTable::unlockAll() {
  MOZ_ASSERT(allPartitionsLocked);

  for (size_t i = 0; i < PartitionCount; i++) {
    partitions[PartitionCount - i - 1]->lock.unlock();
  }

#ifdef DEBUG
  allPartitionsLocked = false;
#endif
}

MOZ_ALWAYS_INLINE size_t
AtomsTable::getPartitionIndex(const AtomHasher::Lookup& lookup) {
  size_t index = lookup.hash >> (32 - PartitionShift);
  MOZ_ASSERT(index < PartitionCount);
  return index;
}

inline void AtomsTable::tracePinnedAtomsInSet(JSTracer* trc, AtomSet& atoms) {
  for (auto r = atoms.all(); !r.empty(); r.popFront()) {
    const AtomStateEntry& entry = r.front();
    MOZ_ASSERT(entry.isPinned() == entry.asPtrUnbarriered()->isPinned());
    MOZ_DIAGNOSTIC_ASSERT(entry.asPtrUnbarriered());
    if (entry.isPinned()) {
      JSAtom* atom = entry.asPtrUnbarriered();
      TraceRoot(trc, &atom, "interned_atom");
      MOZ_ASSERT(entry.asPtrUnbarriered() == atom);
    }
  }
}

void AtomsTable::tracePinnedAtoms(JSTracer* trc,
                                  const AutoAccessAtomsZone& access) {
  for (size_t i = 0; i < PartitionCount; i++) {
    Partition& part = *partitions[i];
    tracePinnedAtomsInSet(trc, part.atoms);
    if (part.atomsAddedWhileSweeping) {
      tracePinnedAtomsInSet(trc, *part.atomsAddedWhileSweeping);
    }
  }
}

void js::TraceAtoms(JSTracer* trc, const AutoAccessAtomsZone& access) {
  JSRuntime* rt = trc->runtime();
  if (rt->permanentAtomsPopulated()) {
    rt->atoms().tracePinnedAtoms(trc, access);
  }
}

static void TracePermanentAtoms(JSTracer* trc, AtomSet::Range atoms) {
  for (; !atoms.empty(); atoms.popFront()) {
    const AtomStateEntry& entry = atoms.front();
    JSAtom* atom = entry.asPtrUnbarriered();
    MOZ_ASSERT(atom->isPinned());
    TraceProcessGlobalRoot(trc, atom, "permanent atom");
  }
}

void JSRuntime::tracePermanentAtoms(JSTracer* trc) {
  // Permanent atoms only need to be traced in the runtime which owns them.
  if (parentRuntime) {
    return;
  }

  // Static strings are not included in the permanent atoms table.
  if (staticStrings) {
    staticStrings->trace(trc);
  }

  if (permanentAtomsDuringInit_) {
    TracePermanentAtoms(trc, permanentAtomsDuringInit_->all());
  }

  if (permanentAtoms_) {
    TracePermanentAtoms(trc, permanentAtoms_->all());
  }
}

void js::TraceWellKnownSymbols(JSTracer* trc) {
  JSRuntime* rt = trc->runtime();

  if (rt->parentRuntime) {
    return;
  }

  if (WellKnownSymbols* wks = rt->wellKnownSymbols) {
    for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
      TraceProcessGlobalRoot(trc, wks->get(i).get(), "well_known_symbol");
    }
  }
}

void AtomsTable::traceWeak(JSTracer* trc) {
  JSRuntime* rt = trc->runtime();
  for (size_t i = 0; i < PartitionCount; i++) {
    AutoLock lock(rt, partitions[i]->lock);
    AtomSet& atoms = partitions[i]->atoms;
    for (AtomSet::Enum e(atoms); !e.empty(); e.popFront()) {
      JSAtom* atom = e.front().asPtrUnbarriered();
      MOZ_DIAGNOSTIC_ASSERT(atom);
      if (!TraceManuallyBarrieredWeakEdge(trc, &atom,
                                          "AtomsTable::partitions::atoms")) {
        e.removeFront();
      } else {
        MOZ_ASSERT(atom == e.front().asPtrUnbarriered());
      }
    }
  }
}

AtomsTable::SweepIterator::SweepIterator(AtomsTable& atoms)
    : atoms(atoms), partitionIndex(0) {
  startSweepingPartition();
  settle();
}

inline void AtomsTable::SweepIterator::startSweepingPartition() {
  MOZ_ASSERT(atoms.partitions[partitionIndex]->atomsAddedWhileSweeping);
  atomsIter.emplace(atoms.partitions[partitionIndex]->atoms);
}

inline void AtomsTable::SweepIterator::finishSweepingPartition() {
  atomsIter.reset();
  atoms.mergeAtomsAddedWhileSweeping(*atoms.partitions[partitionIndex]);
}

inline void AtomsTable::SweepIterator::settle() {
  MOZ_ASSERT(!empty());

  while (atomsIter->empty()) {
    finishSweepingPartition();
    partitionIndex++;
    if (empty()) {
      return;
    }
    startSweepingPartition();
  }
}

inline bool AtomsTable::SweepIterator::empty() const {
  return partitionIndex == PartitionCount;
}

inline JSAtom* AtomsTable::SweepIterator::front() const {
  MOZ_ASSERT(!empty());
  return atomsIter->front().asPtrUnbarriered();
}

inline void AtomsTable::SweepIterator::removeFront() {
  MOZ_ASSERT(!empty());
  return atomsIter->removeFront();
}

inline void AtomsTable::SweepIterator::popFront() {
  MOZ_ASSERT(!empty());
  atomsIter->popFront();
  settle();
}

bool AtomsTable::startIncrementalSweep() {
  MOZ_ASSERT(JS::RuntimeHeapIsCollecting());

  bool ok = true;
  for (size_t i = 0; i < PartitionCount; i++) {
    auto& part = *partitions[i];

    auto newAtoms = js_new<AtomSet>();
    if (!newAtoms) {
      ok = false;
      break;
    }

    MOZ_ASSERT(!part.atomsAddedWhileSweeping);
    part.atomsAddedWhileSweeping = newAtoms;
  }

  if (!ok) {
    for (size_t i = 0; i < PartitionCount; i++) {
      auto& part = *partitions[i];
      js_delete(part.atomsAddedWhileSweeping);
      part.atomsAddedWhileSweeping = nullptr;
    }
  }

  return ok;
}

void AtomsTable::mergeAtomsAddedWhileSweeping(Partition& part) {
  // Add atoms that were added to the secondary table while we were sweeping
  // the main table.

  AutoEnterOOMUnsafeRegion oomUnsafe;

  auto newAtoms = part.atomsAddedWhileSweeping;
  part.atomsAddedWhileSweeping = nullptr;

  for (auto r = newAtoms->all(); !r.empty(); r.popFront()) {
    if (!part.atoms.putNew(AtomHasher::Lookup(r.front().asPtrUnbarriered()),
                           r.front())) {
      oomUnsafe.crash("Adding atom from secondary table after sweep");
    }
  }

  js_delete(newAtoms);
}

bool AtomsTable::sweepIncrementally(SweepIterator& atomsToSweep,
                                    SliceBudget& budget) {
  // Sweep the table incrementally until we run out of work or budget.
  while (!atomsToSweep.empty()) {
    budget.step();
    if (budget.isOverBudget()) {
      return false;
    }

    JSAtom* atom = atomsToSweep.front();
    MOZ_DIAGNOSTIC_ASSERT(atom);
    // TODO: Bug 1574981 - investigate talos regression in
    // AtomsTable::sweepIncrementally
    if (IsAboutToBeFinalizedUnbarriered(&atom)) {
      MOZ_ASSERT(!atom->isPinned());
      atomsToSweep.removeFront();
    } else {
      MOZ_ASSERT(atom == atomsToSweep.front());
    }
    atomsToSweep.popFront();
  }

  for (size_t i = 0; i < PartitionCount; i++) {
    MOZ_ASSERT(!partitions[i]->atomsAddedWhileSweeping);
  }

  return true;
}

size_t AtomsTable::sizeOfIncludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  size_t size = sizeof(AtomsTable);
  for (size_t i = 0; i < PartitionCount; i++) {
    size += sizeof(Partition);
    size += partitions[i]->atoms.shallowSizeOfExcludingThis(mallocSizeOf);
  }
  return size;
}

bool JSRuntime::initMainAtomsTables(JSContext* cx) {
  MOZ_ASSERT(!parentRuntime);
  MOZ_ASSERT(!permanentAtomsPopulated());

  // The permanent atoms table has now been populated.
  permanentAtoms_ =
      js_new<FrozenAtomSet>(permanentAtomsDuringInit_);  // Takes ownership.
  permanentAtomsDuringInit_ = nullptr;

  // Initialize the main atoms table.
  MOZ_ASSERT(!atoms_);
  atoms_ = js_new<AtomsTable>();
  return atoms_ && atoms_->init();
}

template <typename Chars>
static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup(
    JSContext* cx, Chars chars, size_t length, const AtomHasher::Lookup& lookup,
    PinningBehavior pin, const Maybe<uint32_t>& indexValue);

template <typename CharT, typename = std::enable_if_t<!std::is_const_v<CharT>>>
static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup(
    JSContext* cx, CharT* chars, size_t length,
    const AtomHasher::Lookup& lookup, PinningBehavior pin,
    const Maybe<uint32_t>& indexValue) {
  return AtomizeAndCopyCharsFromLookup(cx, const_cast<const CharT*>(chars),
                                       length, lookup, pin, indexValue);
}

template <typename Chars>
static MOZ_NEVER_INLINE JSAtom* PermanentlyAtomizeAndCopyChars(
    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, Chars chars, size_t length,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup);

template <typename CharT, typename = std::enable_if_t<!std::is_const_v<CharT>>>
static JSAtom* PermanentlyAtomizeAndCopyChars(
    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, CharT* chars, size_t length,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
  return PermanentlyAtomizeAndCopyChars(
      cx, zonePtr, const_cast<const CharT*>(chars), length, indexValue, lookup);
}

template <typename Chars>
static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup(
    JSContext* cx, Chars chars, size_t length, const AtomHasher::Lookup& lookup,
    PinningBehavior pin, const Maybe<uint32_t>& indexValue) {
  // Try the per-Zone cache first. If we find the atom there we can avoid the
  // atoms lock, the markAtom call, and the multiple HashSet lookups below.
  // We don't use the per-Zone cache if we want a pinned atom: handling that
  // is more complicated and pinning atoms is relatively uncommon.
  Zone* zone = cx->zone();
  Maybe<AtomSet::AddPtr> zonePtr;
  if (MOZ_LIKELY(zone && pin == DoNotPinAtom)) {
    zonePtr.emplace(zone->atomCache().lookupForAdd(lookup));
    if (zonePtr.ref()) {
      // The cache is purged on GC so if we're in the middle of an
      // incremental GC we should have barriered the atom when we put
      // it in the cache.
      JSAtom* atom = zonePtr.ref()->asPtrUnbarriered();
      MOZ_ASSERT(AtomIsMarked(zone, atom));
      return atom;
    }
  }

  // This function can be called during initialization, while the permanent
  // atoms table is being created. In this case all atoms created are added to
  // the permanent atoms table.
  if (!cx->permanentAtomsPopulated()) {
    return PermanentlyAtomizeAndCopyChars(cx, zonePtr, chars, length,
                                          indexValue, lookup);
  }

  AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
  if (pp) {
    JSAtom* atom = pp->asPtr(cx);
    if (zonePtr && MOZ_UNLIKELY(!zone->atomCache().add(
                       *zonePtr, AtomStateEntry(atom, false)))) {
      ReportOutOfMemory(cx);
      return nullptr;
    }

    return atom;
  }

  // Validate the length before taking an atoms partition lock, as throwing an
  // exception here may reenter this code.
  if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) {
    return nullptr;
  }

  JSAtom* atom = cx->atoms().atomizeAndCopyChars(cx, chars, length, pin,
                                                 indexValue, lookup);
  if (!atom) {
    return nullptr;
  }

  if (MOZ_UNLIKELY(!cx->atomMarking().inlinedMarkAtomFallible(cx, atom))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  if (zonePtr && MOZ_UNLIKELY(!zone->atomCache().add(
                     *zonePtr, AtomStateEntry(atom, false)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return atom;
}

template <typename Chars>
static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom(
    JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup);

template <typename CharT, typename = std::enable_if_t<!std::is_const_v<CharT>>>
static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom(
    JSContext* cx, CharT* chars, size_t length, PinningBehavior pin,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
  return AllocateNewAtom(cx, const_cast<const CharT*>(chars), length, pin,
                         indexValue, lookup);
}

template <typename Chars>
MOZ_ALWAYS_INLINE JSAtom* AtomsTable::atomizeAndCopyChars(
    JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
  Partition& part = *partitions[getPartitionIndex(lookup)];
  AutoLock lock(cx->runtime(), part.lock);

  AtomSet& atoms = part.atoms;
  AtomSet* atomsAddedWhileSweeping = part.atomsAddedWhileSweeping;
  AtomSet::AddPtr p;

  if (!atomsAddedWhileSweeping) {
    p = atoms.lookupForAdd(lookup);
  } else {
    // We're currently sweeping the main atoms table and all new atoms will
    // be added to a secondary table. Check this first.
    p = atomsAddedWhileSweeping->lookupForAdd(lookup);

    // If that fails check the main table but check if any atom found there
    // is dead.
    if (!p) {
      if (AtomSet::AddPtr p2 = atoms.lookupForAdd(lookup)) {
        JSAtom* atom = p2->asPtrUnbarriered();
        if (!IsAboutToBeFinalizedUnbarriered(&atom)) {
          p = p2;
        }
      }
    }
  }

  if (p) {
    JSAtom* atom = p->asPtr(cx);
    if (pin && !atom->isPinned()) {
      atom->setPinned();
      p->setPinned(true);
    }
    return atom;
  }

  JSAtom* atom = AllocateNewAtom(cx, chars, length, pin, indexValue, lookup);
  if (!atom) {
    return nullptr;
  }

  // We have held the lock since looking up p, and the operations we've done
  // since then can't GC; therefore the atoms table has not been modified and
  // p is still valid.
  AtomSet* addSet =
      part.atomsAddedWhileSweeping ? part.atomsAddedWhileSweeping : &atoms;
  if (MOZ_UNLIKELY(!addSet->add(p, AtomStateEntry(atom, bool(pin))))) {
    ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
    return nullptr;
  }

  return atom;
}

/* |chars| must not point into an inline or short string. */
template <typename CharT>
static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyChars(
    JSContext* cx, const CharT* chars, size_t length, PinningBehavior pin,
    const Maybe<uint32_t>& indexValue) {
  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
    return s;
  }

  AtomHasher::Lookup lookup(chars, length);
  return AtomizeAndCopyCharsFromLookup(cx, chars, length, lookup, pin,
                                       indexValue);
}

template <typename Chars>
static MOZ_NEVER_INLINE JSAtom* PermanentlyAtomizeAndCopyChars(
    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, Chars chars, size_t length,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
  MOZ_ASSERT(!cx->permanentAtomsPopulated());
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));

  JSRuntime* rt = cx->runtime();
  AtomSet& atoms = *rt->permanentAtomsDuringInit();
  AtomSet::AddPtr p = atoms.lookupForAdd(lookup);
  if (p) {
    return p->asPtr(cx);
  }

  JSAtom* atom =
      AllocateNewAtom(cx, chars, length, DoNotPinAtom, indexValue, lookup);
  if (!atom) {
    return nullptr;
  }

  atom->morphIntoPermanentAtom();

  // We are single threaded at this point, and the operations we've done since
  // then can't GC; therefore the atoms table has not been modified and p is
  // still valid.
  if (!atoms.add(p, AtomStateEntry(atom, true))) {
    ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
    return nullptr;
  }

  if (zonePtr && MOZ_UNLIKELY(!cx->zone()->atomCache().add(
                     *zonePtr, AtomStateEntry(atom, false)))) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return atom;
}

template <typename CharsT>
struct AtomizeUTF8OrWTF8CharsWrapper {
  CharsT utf8;
  JS::SmallestEncoding encoding;

  AtomizeUTF8OrWTF8CharsWrapper(const CharsT& chars,
                                JS::SmallestEncoding minEncode)
      : utf8(chars), encoding(minEncode) {}
};

// MakeLinearStringForAtomization has 4 variants.
// This is used by Latin1Char and char16_t.
template <typename CharT>
static MOZ_ALWAYS_INLINE JSLinearString* MakeLinearStringForAtomization(
    JSContext* cx, const CharT* chars, size_t length) {
  return NewStringCopyN<NoGC>(cx, chars, length);
}

// MakeLinearStringForAtomization has one further variant -- a non-template
// overload accepting LittleEndianChars.
static MOZ_ALWAYS_INLINE JSLinearString* MakeLinearStringForAtomization(
    JSContext* cx, LittleEndianChars chars, size_t length) {
  return NewStringFromLittleEndianNoGC(cx, chars, length);
}

template <typename CharT, typename WrapperT>
static MOZ_ALWAYS_INLINE JSLinearString* MakeUTF8AtomHelper(
    JSContext* cx, const WrapperT* chars, size_t length) {
  if (JSInlineString::lengthFits<CharT>(length)) {
    CharT* storage;
    JSInlineString* str = AllocateInlineString<NoGC>(cx, length, &storage);
    if (!str) {
      return nullptr;
    }

    InflateUTF8CharsToBufferAndTerminate(chars->utf8, storage, length,
                                         chars->encoding);
    return str;
  }

  // MakeAtomUTF8Helper is called from deep in the Atomization path, which
  // expects functions to fail gracefully with nullptr on OOM, without throwing.
  //
  // Flat strings are null-terminated. Leave room with length + 1
  UniquePtr<CharT[], JS::FreePolicy> newStr(
      js_pod_arena_malloc<CharT>(js::StringBufferArena, length + 1));
  if (!newStr) {
    return nullptr;
  }

  InflateUTF8CharsToBufferAndTerminate(chars->utf8, newStr.get(), length,
                                       chars->encoding);

  return JSLinearString::new_<NoGC>(cx, std::move(newStr), length);
}

// Another 2 variants of MakeLinearStringForAtomization.
// This is used by AtomizeUTF8OrWTF8CharsWrapper with UTF8Chars or WTF8Chars.
template <typename InputCharsT>
/* static */ MOZ_ALWAYS_INLINE JSLinearString* MakeLinearStringForAtomization(
    JSContext* cx, const AtomizeUTF8OrWTF8CharsWrapper<InputCharsT>* chars,
    size_t length) {
  if (length == 0) {
    return cx->emptyString();
  }

  if (chars->encoding == JS::SmallestEncoding::UTF16) {
    return MakeUTF8AtomHelper<char16_t>(cx, chars, length);
  }
  return MakeUTF8AtomHelper<JS::Latin1Char>(cx, chars, length);
}

template <typename Chars>
static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom(
    JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
  AutoAllocInAtomsZone ac(cx);

  JSLinearString* linear = MakeLinearStringForAtomization(cx, chars, length);
  if (!linear) {
    // Grudgingly forgo last-ditch GC. The alternative would be to release
    // the lock, manually GC here, and retry from the top.
    ReportOutOfMemory(cx);
    return nullptr;
  }

  JSAtom* atom = linear->morphAtomizedStringIntoAtom(lookup.hash);
  MOZ_ASSERT(atom->hash() == lookup.hash);

  if (pin) {
    atom->setPinned();
  }

  if (indexValue) {
    atom->maybeInitializeIndex(*indexValue, true);
  }

  return atom;
}

JSAtom* js::AtomizeString(JSContext* cx, JSString* str,
                          js::PinningBehavior pin /* = js::DoNotPinAtom */) {
  if (str->isAtom()) {
    JSAtom& atom = str->asAtom();
    /* N.B. static atoms are effectively always interned. */
    if (pin == PinAtom && !atom.isPinned()) {
      cx->runtime()->atoms().pinExistingAtom(cx, &atom);
    }

    return &atom;
  }

  JSLinearString* linear = str->ensureLinear(cx);
  if (!linear) {
    return nullptr;
  }

  Maybe<uint32_t> indexValue;
  if (str->hasIndexValue()) {
    indexValue.emplace(str->getIndexValue());
  }

  JS::AutoCheckCannotGC nogc;
  return linear->hasLatin1Chars()
             ? AtomizeAndCopyChars(cx, linear->latin1Chars(nogc),
                                   linear->length(), pin, indexValue)
             : AtomizeAndCopyChars(cx, linear->twoByteChars(nogc),
                                   linear->length(), pin, indexValue);
}

void AtomsTable::pinExistingAtom(JSContext* cx, JSAtom* atom) {
  MOZ_ASSERT(atom);
  MOZ_ASSERT(!atom->isPinned());

  AtomHasher::Lookup lookup(atom);

  AtomsTable::Partition& part = *partitions[getPartitionIndex(lookup)];
  AtomsTable::AutoLock lock(cx->runtime(), part.lock);
  AtomSet::Ptr p = part.atoms.lookup(lookup);
  if (!p && part.atomsAddedWhileSweeping) {
    p = part.atomsAddedWhileSweeping->lookup(lookup);
  }

  MOZ_ASSERT(p);  // Unpinned atoms must exist in atoms table.
  MOZ_ASSERT(p->asPtrUnbarriered() == atom);

  atom->setPinned();
  p->setPinned(true);
}

JSAtom* js::Atomize(JSContext* cx, const char* bytes, size_t length,
                    PinningBehavior pin, const Maybe<uint32_t>& indexValue) {
  CHECK_THREAD(cx);

  const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(bytes);
  return AtomizeAndCopyChars(cx, chars, length, pin, indexValue);
}

template <typename CharT>
JSAtom* js::AtomizeChars(JSContext* cx, const CharT* chars, size_t length,
                         PinningBehavior pin) {
  CHECK_THREAD(cx);
  return AtomizeAndCopyChars(cx, chars, length, pin, Nothing());
}

template JSAtom* js::AtomizeChars(JSContext* cx, const Latin1Char* chars,
                                  size_t length, PinningBehavior pin);

template JSAtom* js::AtomizeChars(JSContext* cx, const char16_t* chars,
                                  size_t length, PinningBehavior pin);

template <typename CharsT>
JSAtom* AtomizeUTF8OrWTF8Chars(JSContext* cx, const char* utf8Chars,
                               size_t utf8ByteLength) {
  {
    // Permanent atoms,|JSRuntime::atoms_|, and  static strings are disjoint
    // sets.  |AtomizeAndCopyCharsFromLookup| only consults the first two sets,
    // so we must map any static strings ourselves.  See bug 1575947.
    StaticStrings& statics = cx->staticStrings();

    // Handle all pure-ASCII UTF-8 static strings.
    if (JSAtom* s = statics.lookup(utf8Chars, utf8ByteLength)) {
      return s;
    }

    // The only non-ASCII static strings are the single-code point strings
    // U+0080 through U+00FF, encoded as
    //
    //   0b1100'00xx 0b10xx'xxxx
    //
    // where the encoded code point is the concatenation of the 'x' bits -- and
    // where the highest 'x' bit is necessarily 1 (because U+0080 through U+00FF
    // all contain an 0x80 bit).
    if (utf8ByteLength == 2) {
      auto first = static_cast<uint8_t>(utf8Chars[0]);
      if ((first & 0b1111'1110) == 0b1100'0010) {
        auto second = static_cast<uint8_t>(utf8Chars[1]);
        if (mozilla::IsTrailingUnit(mozilla::Utf8Unit(second))) {
          uint8_t unit =
              static_cast<uint8_t>(first << 6) | (second & 0b0011'1111);

          MOZ_ASSERT(StaticStrings::hasUnit(unit));
          return statics.getUnit(unit);
        }
      }

      // Fallthrough code handles the cases where the two units aren't a Latin-1
      // code point or are invalid.
    }
  }

  size_t length;
  HashNumber hash;
  JS::SmallestEncoding forCopy;
  CharsT utf8(utf8Chars, utf8ByteLength);
  if (!GetUTF8AtomizationData(cx, utf8, &length, &forCopy, &hash)) {
    return nullptr;
  }

  AtomizeUTF8OrWTF8CharsWrapper<CharsT> chars(utf8, forCopy);
  AtomHasher::Lookup lookup(utf8Chars, utf8ByteLength, length, hash);
  if (std::is_same_v<CharsT, WTF8Chars>) {
    lookup.type = AtomHasher::Lookup::WTF8;
  }
  return AtomizeAndCopyCharsFromLookup(cx, &chars, length, lookup, DoNotPinAtom,
                                       Nothing());
}

JSAtom* js::AtomizeUTF8Chars(JSContext* cx, const char* utf8Chars,
                             size_t utf8ByteLength) {
  return AtomizeUTF8OrWTF8Chars<UTF8Chars>(cx, utf8Chars, utf8ByteLength);
}

JSAtom* js::AtomizeWTF8Chars(JSContext* cx, const char* wtf8Chars,
                             size_t wtf8ByteLength) {
  return AtomizeUTF8OrWTF8Chars<WTF8Chars>(cx, wtf8Chars, wtf8ByteLength);
}

bool js::IndexToIdSlow(JSContext* cx, uint32_t index, MutableHandleId idp) {
  MOZ_ASSERT(index > JSID_INT_MAX);

  char16_t buf[UINT32_CHAR_BUFFER_LENGTH];
  RangedPtr<char16_t> end(ArrayEnd(buf), buf, ArrayEnd(buf));
  RangedPtr<char16_t> start = BackfillIndexInCharBuffer(index, end);

  JSAtom* atom = AtomizeChars(cx, start.get(), end - start);
  if (!atom) {
    return false;
  }

  idp.set(JS::PropertyKey::fromNonIntAtom(atom));
  return true;
}

template <AllowGC allowGC>
static JSAtom* ToAtomSlow(
    JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg) {
  MOZ_ASSERT(!arg.isString());

  Value v = arg;
  if (!v.isPrimitive()) {
    MOZ_ASSERT(!cx->isHelperThreadContext());
    if (!allowGC) {
      return nullptr;
    }
    RootedValue v2(cx, v);
    if (!ToPrimitive(cx, JSTYPE_STRING, &v2)) {
      return nullptr;
    }
    v = v2;
  }

  if (v.isString()) {
    JSAtom* atom = AtomizeString(cx, v.toString());
    if (!allowGC && !atom) {
      cx->recoverFromOutOfMemory();
    }
    return atom;
  }
  if (v.isInt32()) {
    JSAtom* atom = Int32ToAtom(cx, v.toInt32());
    if (!allowGC && !atom) {
      cx->recoverFromOutOfMemory();
    }
    return atom;
  }
  if (v.isDouble()) {
    JSAtom* atom = NumberToAtom(cx, v.toDouble());
    if (!allowGC && !atom) {
      cx->recoverFromOutOfMemory();
    }
    return atom;
  }
  if (v.isBoolean()) {
    return v.toBoolean() ? cx->names().true_ : cx->names().false_;
  }
  if (v.isNull()) {
    return cx->names().null;
  }
  if (v.isSymbol()) {
    MOZ_ASSERT(!cx->isHelperThreadContext());
    if (allowGC) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_SYMBOL_TO_STRING);
    }
    return nullptr;
  }
  if (v.isBigInt()) {
    RootedBigInt i(cx, v.toBigInt());
    JSAtom* atom = BigIntToAtom<allowGC>(cx, i);
    if (!allowGC && !atom) {
      cx->recoverFromOutOfMemory();
    }
    return atom;
  }
  MOZ_ASSERT(v.isUndefined());
  return cx->names().undefined;
}

template <AllowGC allowGC>
JSAtom* js::ToAtom(JSContext* cx,
                   typename MaybeRooted<Value, allowGC>::HandleType v) {
  if (!v.isString()) {
    return ToAtomSlow<allowGC>(cx, v);
  }

  JSString* str = v.toString();
  if (str->isAtom()) {
    return &str->asAtom();
  }

  JSAtom* atom = AtomizeString(cx, str);
  if (!atom && !allowGC) {
    MOZ_ASSERT_IF(!cx->isHelperThreadContext(), cx->isThrowingOutOfMemory());
    cx->recoverFromOutOfMemory();
  }
  return atom;
}

template JSAtom* js::ToAtom<CanGC>(JSContext* cx, HandleValue v);

template JSAtom* js::ToAtom<NoGC>(JSContext* cx, const Value& v);

static JSAtom* AtomizeLittleEndianTwoByteChars(JSContext* cx,
                                               const uint8_t* leTwoByte,
                                               size_t length) {
  CHECK_THREAD(cx);

  LittleEndianChars chars(leTwoByte);

  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
    return s;
  }

  AtomHasher::Lookup lookup(chars, length);
  return AtomizeAndCopyCharsFromLookup(cx, chars, length, lookup, DoNotPinAtom,
                                       Nothing());
}

template <XDRMode mode>
XDRResult js::XDRAtomOrNull(XDRState<mode>* xdr, MutableHandleAtom atomp) {
  uint8_t isNull = false;
  if (mode == XDR_ENCODE) {
    if (!atomp) {
      isNull = true;
    }
  }

  MOZ_TRY(xdr->codeUint8(&isNull));

  if (!isNull) {
    MOZ_TRY(XDRAtom(xdr, atomp));
  } else if (mode == XDR_DECODE) {
    atomp.set(nullptr);
  }

  return Ok();
}

template XDRResult js::XDRAtomOrNull(XDRState<XDR_DECODE>* xdr,
                                     MutableHandleAtom atomp);

template XDRResult js::XDRAtomOrNull(XDRState<XDR_ENCODE>* xdr,
                                     MutableHandleAtom atomp);

template <XDRMode mode>
static XDRResult XDRAtomIndex(XDRState<mode>* xdr, uint32_t* index) {
  return xdr->codeUint32(index);
}

template <XDRMode mode>
XDRResult js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp) {
  if (!xdr->hasAtomMap() && !xdr->hasAtomTable()) {
    return XDRAtomData(xdr, atomp);
  }

  if (mode == XDR_ENCODE) {
    MOZ_ASSERT(xdr->hasAtomMap());

    // Atom contents are encoded in a separate buffer, which is joined to the
    // final result in XDRIncrementalEncoder::linearize. References to atoms
    // are encoded as indices into the atom stream.
    uint32_t atomIndex;
    XDRAtomMap::AddPtr p = xdr->atomMap().lookupForAdd(atomp.get());
    if (p) {
      atomIndex = p->value();
    } else {
      xdr->switchToAtomBuf();
      MOZ_TRY(XDRAtomData(xdr, atomp));
      xdr->switchToMainBuf();

      atomIndex = xdr->natoms();
      xdr->natoms() += 1;
      if (!xdr->atomMap().add(p, atomp.get(), atomIndex)) {
        return xdr->fail(JS::TranscodeResult_Throw);
      }
    }
    MOZ_TRY(XDRAtomIndex(xdr, &atomIndex));
    return Ok();
  }

  MOZ_ASSERT(mode == XDR_DECODE && xdr->hasAtomTable());

  uint32_t atomIndex;
  MOZ_TRY(XDRAtomIndex(xdr, &atomIndex));
  if (atomIndex >= xdr->atomTable().length()) {
    return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
  }
  JSAtom* atom = xdr->atomTable()[atomIndex];

  atomp.set(atom);
  return Ok();
}

template XDRResult js::XDRAtom(XDRState<XDR_DECODE>* xdr,
                               MutableHandleAtom atomp);

template XDRResult js::XDRAtom(XDRState<XDR_ENCODE>* xdr,
                               MutableHandleAtom atomp);

template <XDRMode mode>
XDRResult js::XDRAtomData(XDRState<mode>* xdr, MutableHandleAtom atomp) {
  bool latin1 = false;
  uint32_t length = 0;
  uint32_t lengthAndEncoding = 0;

  if (mode == XDR_ENCODE) {
    JS::AutoCheckCannotGC nogc;
    static_assert(JSString::MAX_LENGTH <= INT32_MAX,
                  "String length must fit in 31 bits");
    latin1 = atomp->hasLatin1Chars();
    length = atomp->length();
    lengthAndEncoding = (length << 1) | uint32_t(latin1);
    MOZ_TRY(xdr->codeUint32(&lengthAndEncoding));
    if (latin1) {
      return xdr->codeChars(
          const_cast<JS::Latin1Char*>(atomp->latin1Chars(nogc)), length);
    }
    return xdr->codeChars(const_cast<char16_t*>(atomp->twoByteChars(nogc)),
                          length);
  }

  MOZ_ASSERT(mode == XDR_DECODE);
  /* Avoid JSString allocation for already existing atoms. See bug 321985. */
  JSContext* cx = xdr->cx();
  JSAtom* atom = nullptr;
  MOZ_TRY(xdr->codeUint32(&lengthAndEncoding));
  length = lengthAndEncoding >> 1;
  latin1 = lengthAndEncoding & 0x1;

  if (latin1) {
    const Latin1Char* chars = nullptr;
    if (length) {
      const uint8_t* ptr;
      size_t nbyte = length * sizeof(Latin1Char);
      MOZ_TRY(xdr->peekData(&ptr, nbyte));
      chars = reinterpret_cast<const Latin1Char*>(ptr);
    }
    atom = AtomizeChars(cx, chars, length);
  } else {
    const uint8_t* twoByteCharsLE = nullptr;
    if (length) {
      size_t nbyte = length * sizeof(char16_t);
      MOZ_TRY(xdr->peekData(&twoByteCharsLE, nbyte));
    }
    atom = AtomizeLittleEndianTwoByteChars(cx, twoByteCharsLE, length);
  }

  if (!atom) {
    return xdr->fail(JS::TranscodeResult_Throw);
  }
  atomp.set(atom);
  return Ok();
}

template XDRResult js::XDRAtomData(XDRState<XDR_ENCODE>* xdr,
                                   MutableHandleAtom atomp);

template XDRResult js::XDRAtomData(XDRState<XDR_DECODE>* xdr,
                                   MutableHandleAtom atomp);

Handle<PropertyName*> js::ClassName(JSProtoKey key, JSContext* cx) {
  return ClassName(key, cx->names());
}

js::AutoLockAllAtoms::AutoLockAllAtoms(JSRuntime* rt) : runtime(rt) {
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
  if (runtime->hasHelperThreadZones()) {
    runtime->atoms().lockAll();
  }
}

js::AutoLockAllAtoms::~AutoLockAllAtoms() {
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
  if (runtime->hasHelperThreadZones()) {
    runtime->atoms().unlockAll();
  }
}
