tor-browser

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

JSAtomUtils.cpp (33361B)


      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 /*
      8 * JS atom table.
      9 */
     10 
     11 #include "vm/JSAtomUtils-inl.h"
     12 
     13 #include "mozilla/HashFunctions.h"  // mozilla::HashStringKnownLength
     14 #include "mozilla/RangedPtr.h"
     15 
     16 #include <charconv>
     17 #include <string.h>
     18 
     19 #include "jstypes.h"
     20 
     21 #include "frontend/CompilationStencil.h"
     22 #include "gc/GC.h"
     23 #include "gc/Marking.h"
     24 #include "gc/MaybeRooted.h"
     25 #include "js/CharacterEncoding.h"
     26 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     27 #include "js/Symbol.h"
     28 #include "util/Text.h"
     29 #include "vm/JSContext.h"
     30 #include "vm/JSObject.h"
     31 #include "vm/StaticStrings.h"
     32 #include "vm/StringType.h"
     33 #include "vm/SymbolType.h"
     34 #include "vm/WellKnownAtom.h"  // WellKnownAtomInfo, WellKnownAtomId, wellKnownAtomInfos
     35 #include "gc/AtomMarking-inl.h"
     36 #include "vm/JSContext-inl.h"
     37 #include "vm/Realm-inl.h"
     38 #include "vm/StringType-inl.h"
     39 
     40 using namespace js;
     41 
     42 using mozilla::Maybe;
     43 using mozilla::Nothing;
     44 using mozilla::RangedPtr;
     45 
     46 template <typename CharT>
     47 extern void InflateUTF8CharsToBuffer(const JS::UTF8Chars& src, CharT* dst,
     48                                     size_t dstLen,
     49                                     JS::SmallestEncoding encoding);
     50 
     51 template <typename CharT>
     52 extern bool UTF8EqualsChars(const JS::UTF8Chars& utf8, const CharT* chars);
     53 
     54 extern bool GetUTF8AtomizationData(JSContext* cx, const JS::UTF8Chars& utf8,
     55                                   size_t* outlen,
     56                                   JS::SmallestEncoding* encoding,
     57                                   HashNumber* hashNum);
     58 
     59 MOZ_ALWAYS_INLINE bool js::AtomHasher::Lookup::StringsMatch(
     60    const JSAtom& atom) const {
     61  if (atom.hasLatin1Chars()) {
     62    const Latin1Char* keyChars = atom.latin1Chars(nogc);
     63    switch (type) {
     64      case Lookup::Latin1:
     65        return EqualChars(keyChars, latin1Chars, length);
     66      case Lookup::TwoByteChar:
     67        return EqualChars(keyChars, twoByteChars, length);
     68      case Lookup::UTF8: {
     69        JS::UTF8Chars utf8(utf8Bytes, byteLength);
     70        return UTF8EqualsChars(utf8, keyChars);
     71      }
     72    }
     73  }
     74 
     75  const char16_t* keyChars = atom.twoByteChars(nogc);
     76  switch (type) {
     77    case Lookup::Latin1:
     78      return EqualChars(latin1Chars, keyChars, length);
     79    case Lookup::TwoByteChar:
     80      return EqualChars(keyChars, twoByteChars, length);
     81    case Lookup::UTF8: {
     82      JS::UTF8Chars utf8(utf8Bytes, byteLength);
     83      return UTF8EqualsChars(utf8, keyChars);
     84    }
     85  }
     86 
     87  MOZ_ASSERT_UNREACHABLE("AtomHasher::match unknown type");
     88  return false;
     89 }
     90 
     91 inline HashNumber js::AtomHasher::hash(const Lookup& l) { return l.hash; }
     92 
     93 MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const WeakHeapPtr<JSAtom*>& entry,
     94                                             const Lookup& lookup) {
     95  JSAtom* key = entry.unbarrieredGet();
     96  if (lookup.atom) {
     97    return lookup.atom == key;
     98  }
     99  if (key->length() != lookup.length || key->hash() != lookup.hash) {
    100    return false;
    101  }
    102 
    103  return lookup.StringsMatch(*key);
    104 }
    105 
    106 UniqueChars js::AtomToPrintableString(JSContext* cx, JSAtom* atom) {
    107  return QuoteString(cx, atom);
    108 }
    109 
    110 // Use a low initial capacity for the permanent atoms table to avoid penalizing
    111 // runtimes that create a small number of atoms.
    112 static const uint32_t JS_PERMANENT_ATOM_SIZE = 64;
    113 
    114 MOZ_ALWAYS_INLINE AtomSet::Ptr js::FrozenAtomSet::readonlyThreadsafeLookup(
    115    const AtomSet::Lookup& l) const {
    116  return mSet->readonlyThreadsafeLookup(l);
    117 }
    118 
    119 static JSAtom* PermanentlyAtomizeCharsValidLength(JSContext* cx,
    120                                                  AtomSet& atomSet,
    121                                                  mozilla::HashNumber hash,
    122                                                  const Latin1Char* chars,
    123                                                  size_t length);
    124 
    125 bool JSRuntime::initializeAtoms(JSContext* cx) {
    126  JS::AutoAssertNoGC nogc;
    127 
    128  MOZ_ASSERT(!atoms_);
    129  MOZ_ASSERT(!permanentAtoms_);
    130 
    131  if (parentRuntime) {
    132    permanentAtoms_ = parentRuntime->permanentAtoms_;
    133 
    134    staticStrings = parentRuntime->staticStrings;
    135    commonNames = parentRuntime->commonNames;
    136    emptyString = parentRuntime->emptyString;
    137    wellKnownSymbols = parentRuntime->wellKnownSymbols;
    138 
    139    atoms_ = js_new<AtomsTable>();
    140    return bool(atoms_);
    141  }
    142 
    143  // NOTE: There's no GC, but `gc.freezePermanentSharedThings` below contains
    144  //       a function call that's marked as "Can GC".
    145  Rooted<UniquePtr<AtomSet>> atomSet(cx,
    146                                     cx->new_<AtomSet>(JS_PERMANENT_ATOM_SIZE));
    147  if (!atomSet) {
    148    return false;
    149  }
    150 
    151  staticStrings = js_new<StaticStrings>();
    152  if (!staticStrings || !staticStrings->init(cx)) {
    153    return false;
    154  }
    155 
    156  // The bare symbol names are already part of the well-known set, but their
    157  // descriptions are not, so enumerate them here and add them to the initial
    158  // permanent atoms set below.
    159  static const WellKnownAtomInfo symbolDescInfo[] = {
    160 #define COMMON_NAME_INFO(NAME)                                  \
    161  {uint32_t(sizeof("Symbol." #NAME) - 1),                       \
    162   mozilla::HashStringKnownLength("Symbol." #NAME,              \
    163                                  sizeof("Symbol." #NAME) - 1), \
    164   "Symbol." #NAME},
    165      JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INFO)
    166 #undef COMMON_NAME_INFO
    167  };
    168 
    169  commonNames = js_new<JSAtomState>();
    170  if (!commonNames) {
    171    return false;
    172  }
    173 
    174  ImmutableTenuredPtr<PropertyName*>* names =
    175      reinterpret_cast<ImmutableTenuredPtr<PropertyName*>*>(commonNames.ref());
    176  for (size_t i = 0; i < uint32_t(WellKnownAtomId::Limit); i++) {
    177    const auto& info = wellKnownAtomInfos[i];
    178    JSAtom* atom = PermanentlyAtomizeCharsValidLength(
    179        cx, *atomSet, info.hash,
    180        reinterpret_cast<const Latin1Char*>(info.content), info.length);
    181    if (!atom) {
    182      return false;
    183    }
    184    names->init(atom->asPropertyName());
    185    names++;
    186  }
    187 
    188  for (const auto& info : symbolDescInfo) {
    189    JSAtom* atom = PermanentlyAtomizeCharsNonStaticValidLength(
    190        cx, *atomSet, info.hash,
    191        reinterpret_cast<const Latin1Char*>(info.content), info.length);
    192    if (!atom) {
    193      return false;
    194    }
    195    names->init(atom->asPropertyName());
    196    names++;
    197  }
    198  MOZ_ASSERT(uintptr_t(names) == uintptr_t(commonNames + 1));
    199 
    200  emptyString = commonNames->empty_;
    201 
    202  // The self-hosted atoms are those that exist in a self-hosted JS source file,
    203  // but are not defined in any of the well-known atom collections.
    204  if (!cx->runtime()->selfHostStencil_->instantiateSelfHostedAtoms(
    205          cx, *atomSet, cx->runtime()->selfHostStencilInput_->atomCache)) {
    206    return false;
    207  }
    208 
    209  // Create the well-known symbols.
    210  auto wks = js_new<WellKnownSymbols>();
    211  if (!wks) {
    212    return false;
    213  }
    214 
    215  {
    216    // Prevent GC until we have fully initialized the well known symbols table.
    217    // Faster than zeroing the array and null checking during every GC.
    218    gc::AutoSuppressGC nogc(cx);
    219 
    220    ImmutableTenuredPtr<PropertyName*>* descriptions =
    221        commonNames->wellKnownSymbolDescriptions();
    222    ImmutableTenuredPtr<JS::Symbol*>* symbols =
    223        reinterpret_cast<ImmutableTenuredPtr<JS::Symbol*>*>(wks);
    224    for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
    225      JS::Symbol* symbol =
    226          JS::Symbol::newWellKnown(cx, JS::SymbolCode(i), descriptions[i]);
    227      if (!symbol) {
    228        ReportOutOfMemory(cx);
    229        return false;
    230      }
    231      symbols[i].init(symbol);
    232    }
    233 
    234    wellKnownSymbols = wks;
    235  }
    236 
    237  if (!gc.freezeSharedAtomsZone()) {
    238    return false;
    239  }
    240 
    241  // The permanent atoms table has now been populated.
    242  permanentAtoms_ =
    243      js_new<FrozenAtomSet>(atomSet.release());  // Takes ownership.
    244  if (!permanentAtoms_) {
    245    return false;
    246  }
    247 
    248  // Initialize the main atoms table.
    249  atoms_ = js_new<AtomsTable>();
    250  if (!atoms_) {
    251    return false;
    252  }
    253 
    254  return true;
    255 }
    256 
    257 void JSRuntime::finishAtoms() {
    258  js_delete(atoms_.ref());
    259 
    260  if (!parentRuntime) {
    261    js_delete(permanentAtoms_.ref());
    262    js_delete(staticStrings.ref());
    263    js_delete(commonNames.ref());
    264    js_delete(wellKnownSymbols.ref());
    265  }
    266 
    267  atoms_ = nullptr;
    268  permanentAtoms_ = nullptr;
    269  staticStrings = nullptr;
    270  commonNames = nullptr;
    271  wellKnownSymbols = nullptr;
    272  emptyString = nullptr;
    273 }
    274 
    275 AtomsTable::AtomsTable()
    276    : atoms(InitialTableSize), atomsAddedWhileSweeping(nullptr) {}
    277 
    278 AtomsTable::~AtomsTable() { MOZ_ASSERT(!atomsAddedWhileSweeping); }
    279 
    280 void AtomsTable::tracePinnedAtoms(JSTracer* trc) {
    281  for (JSAtom* atom : pinnedAtoms) {
    282    TraceRoot(trc, &atom, "pinned atom");
    283  }
    284 }
    285 
    286 void js::TraceAtoms(JSTracer* trc) {
    287  JSRuntime* rt = trc->runtime();
    288  if (rt->permanentAtomsPopulated()) {
    289    rt->atoms().tracePinnedAtoms(trc);
    290  }
    291 }
    292 
    293 void AtomsTable::traceWeak(JSTracer* trc) {
    294  for (AtomSet::Enum e(atoms); !e.empty(); e.popFront()) {
    295    JSAtom* atom = e.front().unbarrieredGet();
    296    MOZ_DIAGNOSTIC_ASSERT(atom);
    297    if (!TraceManuallyBarrieredWeakEdge(trc, &atom, "AtomsTable::atoms")) {
    298      e.removeFront();
    299    } else {
    300      MOZ_ASSERT(atom == e.front().unbarrieredGet());
    301    }
    302  }
    303 }
    304 
    305 bool AtomsTable::startIncrementalSweep(Maybe<SweepIterator>& atomsToSweepOut) {
    306  MOZ_ASSERT(JS::RuntimeHeapIsCollecting());
    307  MOZ_ASSERT(atomsToSweepOut.isNothing());
    308  MOZ_ASSERT(!atomsAddedWhileSweeping);
    309 
    310  atomsAddedWhileSweeping = js_new<AtomSet>();
    311  if (!atomsAddedWhileSweeping) {
    312    return false;
    313  }
    314 
    315  atomsToSweepOut.emplace(atoms);
    316 
    317  return true;
    318 }
    319 
    320 void AtomsTable::mergeAtomsAddedWhileSweeping() {
    321  // Add atoms that were added to the secondary table while we were sweeping
    322  // the main table.
    323 
    324  AutoEnterOOMUnsafeRegion oomUnsafe;
    325 
    326  auto newAtoms = atomsAddedWhileSweeping;
    327  atomsAddedWhileSweeping = nullptr;
    328 
    329  for (auto r = newAtoms->all(); !r.empty(); r.popFront()) {
    330    if (!atoms.putNew(AtomHasher::Lookup(r.front().unbarrieredGet()),
    331                      r.front())) {
    332      oomUnsafe.crash("Adding atom from secondary table after sweep");
    333    }
    334  }
    335 
    336  js_delete(newAtoms);
    337 }
    338 
    339 bool AtomsTable::sweepIncrementally(SweepIterator& atomsToSweep,
    340                                    JS::SliceBudget& budget) {
    341  // Sweep the table incrementally until we run out of work or budget.
    342  while (!atomsToSweep.empty()) {
    343    budget.step();
    344    if (budget.isOverBudget()) {
    345      return false;
    346    }
    347 
    348    JSAtom* atom = atomsToSweep.front().unbarrieredGet();
    349    MOZ_DIAGNOSTIC_ASSERT(atom);
    350    if (IsAboutToBeFinalizedUnbarriered(atom)) {
    351      MOZ_ASSERT(!atom->isPinned());
    352      atomsToSweep.removeFront();
    353    } else {
    354      MOZ_ASSERT(atom == atomsToSweep.front().unbarrieredGet());
    355    }
    356    atomsToSweep.popFront();
    357  }
    358 
    359  mergeAtomsAddedWhileSweeping();
    360  return true;
    361 }
    362 
    363 size_t AtomsTable::sizeOfIncludingThis(
    364    mozilla::MallocSizeOf mallocSizeOf) const {
    365  size_t size = sizeof(AtomsTable);
    366  size += atoms.shallowSizeOfExcludingThis(mallocSizeOf);
    367  if (atomsAddedWhileSweeping) {
    368    size += atomsAddedWhileSweeping->shallowSizeOfExcludingThis(mallocSizeOf);
    369  }
    370  size += pinnedAtoms.sizeOfExcludingThis(mallocSizeOf);
    371  return size;
    372 }
    373 
    374 template <typename CharT>
    375 static MOZ_ALWAYS_INLINE JSAtom*
    376 AtomizeAndCopyCharsNonStaticValidLengthFromLookup(
    377    JSContext* cx, const CharT* chars, size_t length,
    378    const AtomHasher::Lookup& lookup, const Maybe<uint32_t>& indexValue) {
    379  Zone* zone = cx->zone();
    380  MOZ_ASSERT(zone);
    381 
    382  AtomCacheHashTable* atomCache = zone->atomCache();
    383 
    384  // Try the per-Zone cache first. If we find the atom there we can avoid the
    385  // markAtom call, and the multiple HashSet lookups below.
    386  if (MOZ_LIKELY(atomCache)) {
    387    JSAtom* const cachedAtom = atomCache->lookupForAdd(lookup);
    388    if (cachedAtom) {
    389      // The cache is purged on GC so if we're in the middle of an incremental
    390      // GC we should have barriered the atom when we put it in the cache.
    391      MOZ_ASSERT(AtomIsMarked(zone, cachedAtom));
    392      return cachedAtom;
    393    }
    394  }
    395 
    396  MOZ_ASSERT(cx->permanentAtomsPopulated());
    397 
    398  AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
    399  if (pp) {
    400    JSAtom* atom = pp->get();
    401    if (MOZ_LIKELY(atomCache)) {
    402      atomCache->add(lookup.hash, atom);
    403    }
    404    return atom;
    405  }
    406 
    407  JSAtom* atom = cx->atoms().atomizeAndCopyCharsNonStaticValidLength(
    408      cx, chars, length, indexValue, lookup);
    409  if (!atom) {
    410    return nullptr;
    411  }
    412 
    413  if (MOZ_UNLIKELY(
    414          !cx->atomMarking().inlinedMarkAtomFallible(cx->zone(), atom))) {
    415    ReportOutOfMemory(cx);
    416    return nullptr;
    417  }
    418 
    419  if (MOZ_LIKELY(atomCache)) {
    420    atomCache->add(lookup.hash, atom);
    421  }
    422  return atom;
    423 }
    424 
    425 template <typename CharT>
    426 static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtomNonStaticValidLength(
    427    JSContext* cx, const CharT* chars, size_t length,
    428    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup);
    429 
    430 template <typename CharT>
    431 static MOZ_ALWAYS_INLINE JSAtom* AllocateNewPermanentAtomNonStaticValidLength(
    432    JSContext* cx, const CharT* chars, size_t length,
    433    const AtomHasher::Lookup& lookup);
    434 
    435 template <typename CharT>
    436 MOZ_ALWAYS_INLINE JSAtom* AtomsTable::atomizeAndCopyCharsNonStaticValidLength(
    437    JSContext* cx, const CharT* chars, size_t length,
    438    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
    439  AtomSet::AddPtr p;
    440 
    441  if (!atomsAddedWhileSweeping) {
    442    p = atoms.lookupForAdd(lookup);
    443  } else {
    444    // We're currently sweeping the main atoms table and all new atoms will
    445    // be added to a secondary table. Check this first.
    446    p = atomsAddedWhileSweeping->lookupForAdd(lookup);
    447 
    448    // If that fails check the main table but check if any atom found there
    449    // is dead.
    450    if (!p) {
    451      if (AtomSet::AddPtr p2 = atoms.lookupForAdd(lookup)) {
    452        JSAtom* atom = p2->unbarrieredGet();
    453        if (!IsAboutToBeFinalizedUnbarriered(atom)) {
    454          p = p2;
    455        }
    456      }
    457    }
    458  }
    459 
    460  if (p) {
    461    return p->get();
    462  }
    463 
    464  JSAtom* atom = AllocateNewAtomNonStaticValidLength(cx, chars, length,
    465                                                     indexValue, lookup);
    466  if (!atom) {
    467    return nullptr;
    468  }
    469 
    470  // The operations above can't GC; therefore the atoms table has not been
    471  // modified and p is still valid.
    472  AtomSet* addSet = atomsAddedWhileSweeping ? atomsAddedWhileSweeping : &atoms;
    473  if (MOZ_UNLIKELY(!addSet->add(p, atom))) {
    474    ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
    475    return nullptr;
    476  }
    477 
    478  return atom;
    479 }
    480 
    481 /* |chars| must not point into an inline or short string. */
    482 template <typename CharT>
    483 static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyChars(
    484    JSContext* cx, const CharT* chars, size_t length,
    485    const Maybe<uint32_t>& indexValue, const Maybe<js::HashNumber>& hash) {
    486  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
    487    return s;
    488  }
    489 
    490  if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) {
    491    return nullptr;
    492  }
    493 
    494  if (hash.isSome()) {
    495    AtomHasher::Lookup lookup(hash.value(), chars, length);
    496    return AtomizeAndCopyCharsNonStaticValidLengthFromLookup(
    497        cx, chars, length, lookup, indexValue);
    498  }
    499 
    500  AtomHasher::Lookup lookup(chars, length);
    501  return AtomizeAndCopyCharsNonStaticValidLengthFromLookup(cx, chars, length,
    502                                                           lookup, indexValue);
    503 }
    504 
    505 template <typename CharT>
    506 static MOZ_NEVER_INLINE JSAtom*
    507 PermanentlyAtomizeAndCopyCharsNonStaticValidLength(
    508    JSContext* cx, AtomSet& atomSet, const CharT* chars, size_t length,
    509    const AtomHasher::Lookup& lookup) {
    510  MOZ_ASSERT(!cx->permanentAtomsPopulated());
    511  MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
    512 
    513  AtomSet::AddPtr p = atomSet.lookupForAdd(lookup);
    514  if (p) {
    515    return p->get();
    516  }
    517 
    518  JSAtom* atom =
    519      AllocateNewPermanentAtomNonStaticValidLength(cx, chars, length, lookup);
    520  if (!atom) {
    521    return nullptr;
    522  }
    523 
    524  // We are single threaded at this point, and the operations we've done since
    525  // then can't GC; therefore the atoms table has not been modified and p is
    526  // still valid.
    527  if (!atomSet.add(p, atom)) {
    528    ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
    529    return nullptr;
    530  }
    531 
    532  return atom;
    533 }
    534 
    535 struct AtomizeUTF8CharsWrapper {
    536  JS::UTF8Chars utf8;
    537  JS::SmallestEncoding encoding;
    538 
    539  AtomizeUTF8CharsWrapper(const JS::UTF8Chars& chars,
    540                          JS::SmallestEncoding minEncode)
    541      : utf8(chars), encoding(minEncode) {}
    542 };
    543 
    544 // NewAtomNonStaticValidLength has 3 variants.
    545 // This is used by Latin1Char and char16_t.
    546 template <typename CharT>
    547 static MOZ_ALWAYS_INLINE JSAtom* NewAtomNonStaticValidLength(
    548    JSContext* cx, const CharT* chars, size_t length, js::HashNumber hash) {
    549  return NewAtomCopyNMaybeDeflateValidLength(cx, chars, length, hash);
    550 }
    551 
    552 template <typename CharT>
    553 static MOZ_ALWAYS_INLINE JSAtom* MakeUTF8AtomHelperNonStaticValidLength(
    554    JSContext* cx, const AtomizeUTF8CharsWrapper* chars, size_t length,
    555    js::HashNumber hash) {
    556  if (JSAtom::lengthFitsInline<CharT>(length)) {
    557    CharT* storage;
    558    JSAtom* str = AllocateInlineAtom(cx, length, &storage, hash);
    559    if (!str) {
    560      return nullptr;
    561    }
    562 
    563    InflateUTF8CharsToBuffer(chars->utf8, storage, length, chars->encoding);
    564    return str;
    565  }
    566 
    567  // MakeAtomUTF8Helper is called from deep in the Atomization path, which
    568  // expects functions to fail gracefully with nullptr on OOM, without throwing.
    569  JSString::OwnedChars<CharT> newChars(
    570      AllocAtomCharsValidLength<CharT>(cx, length));
    571  if (!newChars) {
    572    return nullptr;
    573  }
    574 
    575  InflateUTF8CharsToBuffer(chars->utf8, newChars.data(), length,
    576                           chars->encoding);
    577 
    578  return JSAtom::newValidLength<CharT>(cx, newChars, hash);
    579 }
    580 
    581 // Another variant of NewAtomNonStaticValidLength.
    582 static MOZ_ALWAYS_INLINE JSAtom* NewAtomNonStaticValidLength(
    583    JSContext* cx, const AtomizeUTF8CharsWrapper* chars, size_t length,
    584    js::HashNumber hash) {
    585  if (length == 0) {
    586    return cx->emptyString();
    587  }
    588 
    589  if (chars->encoding == JS::SmallestEncoding::UTF16) {
    590    return MakeUTF8AtomHelperNonStaticValidLength<char16_t>(cx, chars, length,
    591                                                            hash);
    592  }
    593  return MakeUTF8AtomHelperNonStaticValidLength<JS::Latin1Char>(cx, chars,
    594                                                                length, hash);
    595 }
    596 
    597 template <typename CharT>
    598 static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtomNonStaticValidLength(
    599    JSContext* cx, const CharT* chars, size_t length,
    600    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
    601  AutoAllocInAtomsZone ac(cx);
    602 
    603  JSAtom* atom = NewAtomNonStaticValidLength(cx, chars, length, lookup.hash);
    604  if (!atom) {
    605    // Grudgingly forgo last-ditch GC. The alternative would be to manually GC
    606    // here, and retry from the top.
    607    ReportOutOfMemory(cx);
    608    return nullptr;
    609  }
    610 
    611  MOZ_ASSERT(atom->hash() == lookup.hash);
    612 
    613  if (indexValue) {
    614    atom->setIsIndex(*indexValue);
    615  } else {
    616    // We need to call isIndexSlow directly to avoid the flag check in isIndex,
    617    // because we still have to initialize that flag.
    618    uint32_t index;
    619    if (atom->isIndexSlow(&index)) {
    620      atom->setIsIndex(index);
    621    }
    622  }
    623 
    624  return atom;
    625 }
    626 
    627 template <typename CharT>
    628 static MOZ_ALWAYS_INLINE JSAtom* AllocateNewPermanentAtomNonStaticValidLength(
    629    JSContext* cx, const CharT* chars, size_t length,
    630    const AtomHasher::Lookup& lookup) {
    631  AutoAllocInAtomsZone ac(cx);
    632 
    633 #ifdef DEBUG
    634  if constexpr (std::is_same_v<CharT, char16_t>) {
    635    // Can call DontDeflate variant.
    636    MOZ_ASSERT(!CanStoreCharsAsLatin1(chars, length));
    637  }
    638 #endif
    639 
    640  JSAtom* atom =
    641      NewAtomCopyNDontDeflateValidLength(cx, chars, length, lookup.hash);
    642  if (!atom) {
    643    // Do not bother with a last-ditch GC here since we are very early in
    644    // startup and there is no potential garbage to collect.
    645    ReportOutOfMemory(cx);
    646    return nullptr;
    647  }
    648  atom->makePermanent();
    649 
    650  MOZ_ASSERT(atom->hash() == lookup.hash);
    651 
    652  uint32_t index;
    653  if (atom->isIndexSlow(&index)) {
    654    atom->setIsIndex(index);
    655  }
    656 
    657  return atom;
    658 }
    659 
    660 JSAtom* js::AtomizeStringSlow(JSContext* cx, JSString* str) {
    661  MOZ_ASSERT(!str->isAtom());
    662 
    663  if (str->isAtomRef()) {
    664    return str->atom();
    665  }
    666 
    667  if (JSAtom* atom = cx->caches().stringToAtomCache.lookup(str)) {
    668    return atom;
    669  }
    670 
    671  JS::Latin1Char flattenRope[StringToAtomCache::MinStringLength];
    672  mozilla::Maybe<StringToAtomCache::AtomTableKey> key;
    673  size_t length = str->length();
    674  if (str->isRope() && length < StringToAtomCache::MinStringLength &&
    675      str->hasLatin1Chars()) {
    676    StringSegmentRange<StringToAtomCache::MinStringLength> iter(cx);
    677    if (iter.init(str)) {
    678      size_t index = 0;
    679      do {
    680        const JSLinearString* s = iter.front();
    681        CopyChars(flattenRope + index, *s);
    682        index += s->length();
    683      } while (iter.popFront() && !iter.empty());
    684 
    685      if (JSAtom* atom = cx->caches().stringToAtomCache.lookupWithRopeChars(
    686              flattenRope, length, key)) {
    687        // Since this cache lookup is based on a string comparison, not object
    688        // identity, need to mark atom explicitly in this case. And this is
    689        // not done in lookup() itself, because #including JSContext.h there
    690        // causes some non-trivial #include ordering issues.
    691        cx->markAtom(atom);
    692        str->tryReplaceWithAtomRef(atom);
    693        return atom;
    694      }
    695    }
    696  }
    697 
    698  Maybe<uint32_t> indexValue;
    699  if (str->hasIndexValue()) {
    700    indexValue.emplace(str->getIndexValue());
    701  }
    702 
    703  JSAtom* atom = nullptr;
    704  if (key.isSome()) {
    705    atom = AtomizeAndCopyChars(cx, key.value().string_, key.value().length_,
    706                               indexValue, mozilla::Some(key.value().hash_));
    707  } else {
    708    JSLinearString* linear = str->ensureLinear(cx);
    709    if (!linear) {
    710      return nullptr;
    711    }
    712 
    713    JS::AutoCheckCannotGC nogc;
    714    atom = linear->hasLatin1Chars()
    715               ? AtomizeAndCopyChars(cx, linear->latin1Chars(nogc),
    716                                     linear->length(), indexValue, Nothing())
    717               : AtomizeAndCopyChars(cx, linear->twoByteChars(nogc),
    718                                     linear->length(), indexValue, Nothing());
    719  }
    720 
    721  if (!atom) {
    722    return nullptr;
    723  }
    724 
    725  if (!str->tryReplaceWithAtomRef(atom)) {
    726    cx->caches().stringToAtomCache.maybePut(str, atom, key);
    727  }
    728 
    729  return atom;
    730 }
    731 
    732 bool js::AtomIsPinned(JSContext* cx, JSAtom* atom) { return atom->isPinned(); }
    733 
    734 bool js::PinAtom(JSContext* cx, JSAtom* atom) {
    735  JS::AutoCheckCannotGC nogc;
    736  return cx->runtime()->atoms().maybePinExistingAtom(cx, atom);
    737 }
    738 
    739 bool AtomsTable::maybePinExistingAtom(JSContext* cx, JSAtom* atom) {
    740  MOZ_ASSERT(atom);
    741 
    742  if (atom->isPinned()) {
    743    return true;
    744  }
    745 
    746  if (!pinnedAtoms.append(atom)) {
    747    return false;
    748  }
    749 
    750  atom->setPinned();
    751  return true;
    752 }
    753 
    754 JSAtom* js::Atomize(JSContext* cx, const char* bytes, size_t length,
    755                    const Maybe<uint32_t>& indexValue) {
    756  const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(bytes);
    757  return AtomizeAndCopyChars(cx, chars, length, indexValue, Nothing());
    758 }
    759 
    760 template <typename CharT>
    761 JSAtom* js::AtomizeChars(JSContext* cx, const CharT* chars, size_t length) {
    762  return AtomizeAndCopyChars(cx, chars, length, Nothing(), Nothing());
    763 }
    764 
    765 template JSAtom* js::AtomizeChars(JSContext* cx, const Latin1Char* chars,
    766                                  size_t length);
    767 
    768 template JSAtom* js::AtomizeChars(JSContext* cx, const char16_t* chars,
    769                                  size_t length);
    770 
    771 JSAtom* js::AtomizeWithoutActiveZone(JSContext* cx, const char* bytes,
    772                                     size_t length) {
    773  // This is used to implement JS_AtomizeAndPinString{N} when called without an
    774  // active zone. This simplifies the normal atomization code because it can
    775  // assume a non-null cx->zone().
    776 
    777  MOZ_ASSERT(!cx->zone());
    778  MOZ_ASSERT(cx->permanentAtomsPopulated());
    779 
    780  const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(bytes);
    781 
    782  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
    783    return s;
    784  }
    785 
    786  if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) {
    787    return nullptr;
    788  }
    789 
    790  AtomHasher::Lookup lookup(chars, length);
    791  if (AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup)) {
    792    return pp->get();
    793  }
    794 
    795  return cx->atoms().atomizeAndCopyCharsNonStaticValidLength(cx, chars, length,
    796                                                             Nothing(), lookup);
    797 }
    798 
    799 /* |chars| must not point into an inline or short string. */
    800 template <typename CharT>
    801 JSAtom* js::AtomizeCharsNonStaticValidLength(JSContext* cx, HashNumber hash,
    802                                             const CharT* chars,
    803                                             size_t length) {
    804  MOZ_ASSERT(!cx->staticStrings().lookup(chars, length));
    805 
    806  AtomHasher::Lookup lookup(hash, chars, length);
    807  return AtomizeAndCopyCharsNonStaticValidLengthFromLookup(cx, chars, length,
    808                                                           lookup, Nothing());
    809 }
    810 
    811 template JSAtom* js::AtomizeCharsNonStaticValidLength(JSContext* cx,
    812                                                      HashNumber hash,
    813                                                      const Latin1Char* chars,
    814                                                      size_t length);
    815 
    816 template JSAtom* js::AtomizeCharsNonStaticValidLength(JSContext* cx,
    817                                                      HashNumber hash,
    818                                                      const char16_t* chars,
    819                                                      size_t length);
    820 
    821 static JSAtom* PermanentlyAtomizeCharsValidLength(JSContext* cx,
    822                                                  AtomSet& atomSet,
    823                                                  HashNumber hash,
    824                                                  const Latin1Char* chars,
    825                                                  size_t length) {
    826  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
    827    return s;
    828  }
    829 
    830  return PermanentlyAtomizeCharsNonStaticValidLength(cx, atomSet, hash, chars,
    831                                                     length);
    832 }
    833 
    834 JSAtom* js::PermanentlyAtomizeCharsNonStaticValidLength(JSContext* cx,
    835                                                        AtomSet& atomSet,
    836                                                        HashNumber hash,
    837                                                        const Latin1Char* chars,
    838                                                        size_t length) {
    839  MOZ_ASSERT(!cx->staticStrings().lookup(chars, length));
    840  MOZ_ASSERT(length <= JSString::MAX_LENGTH);
    841 
    842  AtomHasher::Lookup lookup(hash, chars, length);
    843  return PermanentlyAtomizeAndCopyCharsNonStaticValidLength(cx, atomSet, chars,
    844                                                            length, lookup);
    845 }
    846 
    847 JSAtom* js::AtomizeUTF8Chars(JSContext* cx, const char* utf8Chars,
    848                             size_t utf8ByteLength) {
    849  {
    850    StaticStrings& statics = cx->staticStrings();
    851 
    852    // Handle all pure-ASCII UTF-8 static strings.
    853    if (JSAtom* s = statics.lookup(utf8Chars, utf8ByteLength)) {
    854      return s;
    855    }
    856 
    857    // The only non-ASCII static strings are the single-code point strings
    858    // U+0080 through U+00FF, encoded as
    859    //
    860    //   0b1100'00xx 0b10xx'xxxx
    861    //
    862    // where the encoded code point is the concatenation of the 'x' bits -- and
    863    // where the highest 'x' bit is necessarily 1 (because U+0080 through U+00FF
    864    // all contain an 0x80 bit).
    865    if (utf8ByteLength == 2) {
    866      auto first = static_cast<uint8_t>(utf8Chars[0]);
    867      if ((first & 0b1111'1110) == 0b1100'0010) {
    868        auto second = static_cast<uint8_t>(utf8Chars[1]);
    869        if (mozilla::IsTrailingUnit(mozilla::Utf8Unit(second))) {
    870          uint8_t unit =
    871              static_cast<uint8_t>(first << 6) | (second & 0b0011'1111);
    872 
    873          MOZ_ASSERT(StaticStrings::hasUnit(unit));
    874          return statics.getUnit(unit);
    875        }
    876      }
    877 
    878      // Fallthrough code handles the cases where the two units aren't a Latin-1
    879      // code point or are invalid.
    880    }
    881  }
    882 
    883  size_t length;
    884  HashNumber hash;
    885  JS::SmallestEncoding forCopy;
    886  JS::UTF8Chars utf8(utf8Chars, utf8ByteLength);
    887  if (!GetUTF8AtomizationData(cx, utf8, &length, &forCopy, &hash)) {
    888    return nullptr;
    889  }
    890 
    891  if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) {
    892    return nullptr;
    893  }
    894 
    895  AtomizeUTF8CharsWrapper chars(utf8, forCopy);
    896  AtomHasher::Lookup lookup(utf8Chars, utf8ByteLength, length, hash);
    897  return AtomizeAndCopyCharsNonStaticValidLengthFromLookup(cx, &chars, length,
    898                                                           lookup, Nothing());
    899 }
    900 
    901 bool js::IndexToIdSlow(JSContext* cx, uint32_t index, MutableHandleId idp) {
    902  MOZ_ASSERT(index > JS::PropertyKey::IntMax);
    903 
    904  char buf[UINT32_CHAR_BUFFER_LENGTH];
    905 
    906  auto result = std::to_chars(buf, buf + std::size(buf), index, 10);
    907  MOZ_ASSERT(result.ec == std::errc());
    908 
    909  size_t length = result.ptr - buf;
    910  JSAtom* atom = Atomize(cx, buf, length);
    911  if (!atom) {
    912    return false;
    913  }
    914 
    915  idp.set(JS::PropertyKey::NonIntAtom(atom));
    916  return true;
    917 }
    918 
    919 bool js::IndexToIdSlow(JSContext* cx, uint64_t index, MutableHandleId idp) {
    920  MOZ_ASSERT(index > JS::PropertyKey::IntMax);
    921 
    922  // Plus one to include the largest number.
    923  constexpr size_t UINT64_CHAR_BUFFER_LENGTH =
    924      std::numeric_limits<uint64_t>::digits10 + 1;
    925 
    926  char buf[UINT64_CHAR_BUFFER_LENGTH];
    927 
    928  auto result = std::to_chars(buf, buf + std::size(buf), index, 10);
    929  MOZ_ASSERT(result.ec == std::errc());
    930 
    931  size_t length = result.ptr - buf;
    932  JSAtom* atom = Atomize(cx, buf, length);
    933  if (!atom) {
    934    return false;
    935  }
    936 
    937  idp.set(JS::PropertyKey::NonIntAtom(atom));
    938  return true;
    939 }
    940 
    941 template <AllowGC allowGC>
    942 static MOZ_ALWAYS_INLINE JSAtom* PrimitiveToAtom(JSContext* cx,
    943                                                 const Value& v) {
    944  MOZ_ASSERT(v.isPrimitive());
    945  switch (v.type()) {
    946    case ValueType::String: {
    947      JSAtom* atom = AtomizeString(cx, v.toString());
    948      if (!allowGC && !atom) {
    949        cx->recoverFromOutOfMemory();
    950      }
    951      return atom;
    952    }
    953    case ValueType::Int32: {
    954      JSAtom* atom = Int32ToAtom(cx, v.toInt32());
    955      if (!allowGC && !atom) {
    956        cx->recoverFromOutOfMemory();
    957      }
    958      return atom;
    959    }
    960    case ValueType::Double: {
    961      JSAtom* atom = NumberToAtom(cx, v.toDouble());
    962      if (!allowGC && !atom) {
    963        cx->recoverFromOutOfMemory();
    964      }
    965      return atom;
    966    }
    967    case ValueType::Boolean:
    968      return v.toBoolean() ? cx->names().true_ : cx->names().false_;
    969    case ValueType::Null:
    970      return cx->names().null;
    971    case ValueType::Undefined:
    972      return cx->names().undefined;
    973    case ValueType::Symbol:
    974      if constexpr (allowGC) {
    975        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    976                                  JSMSG_SYMBOL_TO_STRING);
    977      }
    978      return nullptr;
    979    case ValueType::BigInt: {
    980      RootedBigInt i(cx, v.toBigInt());
    981      return BigIntToAtom<allowGC>(cx, i);
    982    }
    983    case ValueType::Object:
    984    case ValueType::Magic:
    985    case ValueType::PrivateGCThing:
    986      break;
    987  }
    988  MOZ_CRASH("Unexpected type");
    989 }
    990 
    991 template <AllowGC allowGC>
    992 static JSAtom* ToAtomSlow(
    993    JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg) {
    994  MOZ_ASSERT(!arg.isString());
    995 
    996  Value v = arg;
    997  if (!v.isPrimitive()) {
    998    if (!allowGC) {
    999      return nullptr;
   1000    }
   1001    RootedValue v2(cx, v);
   1002    if (!ToPrimitive(cx, JSTYPE_STRING, &v2)) {
   1003      return nullptr;
   1004    }
   1005    v = v2;
   1006  }
   1007 
   1008  return PrimitiveToAtom<allowGC>(cx, v);
   1009 }
   1010 
   1011 template <AllowGC allowGC>
   1012 JSAtom* js::ToAtom(JSContext* cx,
   1013                   typename MaybeRooted<Value, allowGC>::HandleType v) {
   1014  if (!v.isString()) {
   1015    return ToAtomSlow<allowGC>(cx, v);
   1016  }
   1017 
   1018  JSAtom* atom = AtomizeString(cx, v.toString());
   1019  if (!atom && !allowGC) {
   1020    MOZ_ASSERT(cx->isThrowingOutOfMemory());
   1021    cx->recoverFromOutOfMemory();
   1022  }
   1023  return atom;
   1024 }
   1025 
   1026 template JSAtom* js::ToAtom<CanGC>(JSContext* cx, HandleValue v);
   1027 template JSAtom* js::ToAtom<NoGC>(JSContext* cx, const Value& v);
   1028 
   1029 template <AllowGC allowGC>
   1030 bool js::PrimitiveValueToIdSlow(
   1031    JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType v,
   1032    typename MaybeRooted<jsid, allowGC>::MutableHandleType idp) {
   1033  MOZ_ASSERT(v.isPrimitive());
   1034  MOZ_ASSERT(!v.isString());
   1035  MOZ_ASSERT(!v.isSymbol());
   1036  MOZ_ASSERT_IF(v.isInt32(), !PropertyKey::fitsInInt(v.toInt32()));
   1037 
   1038  int32_t i;
   1039  if (v.isDouble() && mozilla::NumberEqualsInt32(v.toDouble(), &i) &&
   1040      PropertyKey::fitsInInt(i)) {
   1041    idp.set(PropertyKey::Int(i));
   1042    return true;
   1043  }
   1044 
   1045  JSAtom* atom = PrimitiveToAtom<allowGC>(cx, v);
   1046  if (!atom) {
   1047    return false;
   1048  }
   1049 
   1050  idp.set(AtomToId(atom));
   1051  return true;
   1052 }
   1053 
   1054 template bool js::PrimitiveValueToIdSlow<CanGC>(JSContext* cx, HandleValue v,
   1055                                                MutableHandleId idp);
   1056 template bool js::PrimitiveValueToIdSlow<NoGC>(JSContext* cx, const Value& v,
   1057                                               FakeMutableHandle<jsid> idp);
   1058 
   1059 Handle<PropertyName*> js::ClassName(JSProtoKey key, JSContext* cx) {
   1060  return ClassName(key, cx->names());
   1061 }