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 }