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