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