StringType-inl.h (29157B)
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 #ifndef vm_StringType_inl_h 8 #define vm_StringType_inl_h 9 10 #include "vm/StringType.h" 11 12 #include "mozilla/PodOperations.h" 13 #include "mozilla/Range.h" 14 #include "mozilla/StringBuffer.h" 15 16 #include "gc/GCEnum.h" 17 #include "gc/MaybeRooted.h" 18 #include "gc/StoreBuffer.h" 19 #include "js/UniquePtr.h" 20 #include "vm/StaticStrings.h" 21 22 #include "gc/GCContext-inl.h" 23 #include "gc/Marking-inl.h" 24 #include "gc/StoreBuffer-inl.h" 25 #include "vm/JSContext-inl.h" 26 27 namespace js { 28 29 // Allocate a thin inline string if possible, and a fat inline string if not. 30 template <AllowGC allowGC, typename CharT> 31 static MOZ_ALWAYS_INLINE JSInlineString* AllocateInlineString( 32 JSContext* cx, size_t len, CharT** chars, js::gc::Heap heap) { 33 MOZ_ASSERT(JSInlineString::lengthFits<CharT>(len)); 34 35 if (JSThinInlineString::lengthFits<CharT>(len)) { 36 return cx->newCell<JSThinInlineString, allowGC>(heap, len, chars); 37 } 38 return cx->newCell<JSFatInlineString, allowGC>(heap, len, chars); 39 } 40 41 template <typename CharT> 42 static MOZ_ALWAYS_INLINE JSAtom* AllocateInlineAtom(JSContext* cx, size_t len, 43 CharT** chars, 44 js::HashNumber hash) { 45 MOZ_ASSERT(JSAtom::lengthFitsInline<CharT>(len)); 46 if constexpr (js::ThinInlineAtom::EverInstantiated) { 47 if (js::ThinInlineAtom::lengthFits<CharT>(len)) { 48 return cx->newCell<js::ThinInlineAtom, js::NoGC>(len, chars, hash); 49 } 50 } 51 return cx->newCell<js::FatInlineAtom, js::NoGC>(len, chars, hash); 52 } 53 54 // Create a thin inline string if possible, and a fat inline string if not. 55 template <AllowGC allowGC, typename CharT> 56 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString( 57 JSContext* cx, mozilla::Range<const CharT> chars, 58 js::gc::Heap heap = js::gc::Heap::Default) { 59 /* 60 * Don't bother trying to find a static atom; measurement shows that not 61 * many get here (for one, Atomize is catching them). 62 */ 63 64 size_t len = chars.length(); 65 CharT* storage; 66 JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage, heap); 67 if (!str) { 68 return nullptr; 69 } 70 71 mozilla::PodCopy(storage, chars.begin().get(), len); 72 return str; 73 } 74 75 // Create a thin inline string if possible, and a fat inline string if not. 76 template <AllowGC allowGC, typename CharT, size_t N> 77 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString( 78 JSContext* cx, const CharT (&chars)[N], size_t len, 79 js::gc::Heap heap = js::gc::Heap::Default) { 80 MOZ_ASSERT(len <= N); 81 82 /* 83 * Don't bother trying to find a static atom; measurement shows that not 84 * many get here (for one, Atomize is catching them). 85 */ 86 87 CharT* storage; 88 JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage, heap); 89 if (!str) { 90 return nullptr; 91 } 92 93 if (JSThinInlineString::lengthFits<CharT>(len)) { 94 constexpr size_t MaxLength = std::is_same_v<CharT, Latin1Char> 95 ? JSThinInlineString::MAX_LENGTH_LATIN1 96 : JSThinInlineString::MAX_LENGTH_TWO_BYTE; 97 98 // memcpy with a constant length can be optimized more easily by compilers. 99 constexpr size_t toCopy = std::min(N, MaxLength) * sizeof(CharT); 100 std::memcpy(storage, chars, toCopy); 101 } else { 102 constexpr size_t MaxLength = std::is_same_v<CharT, Latin1Char> 103 ? JSFatInlineString::MAX_LENGTH_LATIN1 104 : JSFatInlineString::MAX_LENGTH_TWO_BYTE; 105 106 // memcpy with a constant length can be optimized more easily by compilers. 107 constexpr size_t toCopy = std::min(N, MaxLength) * sizeof(CharT); 108 std::memcpy(storage, chars, toCopy); 109 } 110 return str; 111 } 112 113 template <typename CharT> 114 static MOZ_ALWAYS_INLINE JSAtom* NewInlineAtom(JSContext* cx, 115 const CharT* chars, 116 size_t length, 117 js::HashNumber hash) { 118 CharT* storage; 119 JSAtom* str = AllocateInlineAtom(cx, length, &storage, hash); 120 if (!str) { 121 return nullptr; 122 } 123 124 mozilla::PodCopy(storage, chars, length); 125 return str; 126 } 127 128 // Create a thin inline string if possible, and a fat inline string if not. 129 template <typename CharT> 130 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString( 131 JSContext* cx, Handle<JSLinearString*> base, size_t start, size_t length, 132 js::gc::Heap heap) { 133 MOZ_ASSERT(JSInlineString::lengthFits<CharT>(length)); 134 135 CharT* chars; 136 JSInlineString* s = AllocateInlineString<CanGC>(cx, length, &chars, heap); 137 if (!s) { 138 return nullptr; 139 } 140 141 JS::AutoCheckCannotGC nogc; 142 mozilla::PodCopy(chars, base->chars<CharT>(nogc) + start, length); 143 return s; 144 } 145 146 template <typename CharT> 147 static MOZ_ALWAYS_INLINE JSLinearString* TryEmptyOrStaticString( 148 JSContext* cx, const CharT* chars, size_t n) { 149 // Measurements on popular websites indicate empty strings are pretty common 150 // and most strings with length 1 or 2 are in the StaticStrings table. For 151 // length 3 strings that's only about 1%, so we check n <= 2. 152 if (n <= 2) { 153 if (n == 0) { 154 return cx->emptyString(); 155 } 156 157 if (JSLinearString* str = cx->staticStrings().lookup(chars, n)) { 158 return str; 159 } 160 } 161 162 return nullptr; 163 } 164 165 } /* namespace js */ 166 167 template <typename CharT> 168 JSString::OwnedChars<CharT>::OwnedChars(CharT* chars, size_t length, Kind kind) 169 : chars_(chars, length), kind_(kind) { 170 MOZ_ASSERT(kind != Kind::Uninitialized); 171 MOZ_ASSERT(length > 0); 172 MOZ_ASSERT(chars); 173 #ifdef DEBUG 174 bool inNursery = js::TlsContext.get()->nursery().isInside(chars); 175 MOZ_ASSERT((kind == Kind::Nursery) == inNursery); 176 #endif 177 } 178 179 template <typename CharT> 180 JSString::OwnedChars<CharT>::OwnedChars(JSString::OwnedChars<CharT>&& other) 181 : chars_(other.chars_), kind_(other.kind_) { 182 other.release(); 183 } 184 185 template <typename CharT> 186 JSString::OwnedChars<CharT>& JSString::OwnedChars<CharT>::operator=( 187 JSString::OwnedChars<CharT>&& other) { 188 reset(); 189 chars_ = other.chars_; 190 kind_ = other.kind_; 191 other.release(); 192 return *this; 193 } 194 195 template <typename CharT> 196 CharT* JSString::OwnedChars<CharT>::release() { 197 CharT* chars = chars_.data(); 198 chars_ = {}; 199 kind_ = Kind::Uninitialized; 200 return chars; 201 } 202 203 template <typename CharT> 204 void JSString::OwnedChars<CharT>::reset() { 205 switch (kind_) { 206 case Kind::Uninitialized: 207 case Kind::Nursery: 208 break; 209 case Kind::Malloc: 210 js_free(chars_.data()); 211 break; 212 case Kind::StringBuffer: 213 mozilla::StringBuffer::FromData(chars_.data())->Release(); 214 break; 215 } 216 chars_ = {}; 217 kind_ = Kind::Uninitialized; 218 } 219 220 template <typename CharT> 221 void JSString::OwnedChars<CharT>::ensureNonNursery() { 222 if (kind_ != Kind::Nursery) { 223 return; 224 } 225 226 js::AutoEnterOOMUnsafeRegion oomUnsafe; 227 CharT* oldPtr = data(); 228 size_t length = chars_.Length(); 229 CharT* ptr = js_pod_arena_malloc<CharT>(js::StringBufferArena, length); 230 if (!ptr) { 231 oomUnsafe.crash(chars_.size(), "moving nursery buffer to heap"); 232 } 233 mozilla::PodCopy(ptr, oldPtr, length); 234 chars_ = mozilla::Span<CharT>(ptr, length); 235 kind_ = Kind::Malloc; 236 } 237 238 template <typename CharT> 239 JSString::OwnedChars<CharT>::OwnedChars( 240 js::UniquePtr<CharT[], JS::FreePolicy>&& chars, size_t length) 241 : OwnedChars(chars.release(), length, Kind::Malloc) {} 242 243 template <typename CharT> 244 JSString::OwnedChars<CharT>::OwnedChars(RefPtr<mozilla::StringBuffer>&& buffer, 245 size_t length) 246 : OwnedChars(static_cast<CharT*>(buffer->Data()), length, 247 Kind::StringBuffer) { 248 // Transfer the reference from |buffer| to this OwnedChars. 249 mozilla::StringBuffer* buf; 250 buffer.forget(&buf); 251 } 252 253 MOZ_ALWAYS_INLINE bool JSString::validateLength(JSContext* cx, size_t length) { 254 return validateLengthInternal<js::CanGC>(cx, length); 255 } 256 257 template <js::AllowGC allowGC> 258 MOZ_ALWAYS_INLINE bool JSString::validateLengthInternal(JSContext* cx, 259 size_t length) { 260 if (MOZ_UNLIKELY(length > JSString::MAX_LENGTH)) { 261 if constexpr (allowGC) { 262 js::ReportOversizedAllocation(cx, JSMSG_ALLOC_OVERFLOW); 263 } 264 return false; 265 } 266 267 return true; 268 } 269 270 template <> 271 MOZ_ALWAYS_INLINE const char16_t* JSString::nonInlineCharsRaw() const { 272 return d.s.u2.nonInlineCharsTwoByte; 273 } 274 275 template <> 276 MOZ_ALWAYS_INLINE const JS::Latin1Char* JSString::nonInlineCharsRaw() const { 277 return d.s.u2.nonInlineCharsLatin1; 278 } 279 280 bool JSString::ownsMallocedChars() const { 281 if (!hasOutOfLineChars() || asLinear().hasStringBuffer()) { 282 return false; 283 } 284 285 js::gc::StoreBuffer* sb = storeBuffer(); 286 if (!sb) { 287 // Tenured strings always own out-of-line chars. 288 return true; 289 } 290 291 // Return whether the chars are malloced. Note: this allows the data to be in 292 // a different nursery chunk than the Cell itself, at the performance cost of 293 // iterating over all chunks. 294 return !sb->nursery().isInside(asLinear().nonInlineCharsRaw()); 295 } 296 297 template <typename CharT> 298 inline size_t JSLinearString::maybeMallocCharsOnPromotion( 299 js::Nursery* nursery) { 300 const void** chars; 301 if constexpr (std::is_same_v<CharT, char16_t>) { 302 chars = reinterpret_cast<const void**>(&d.s.u2.nonInlineCharsTwoByte); 303 } else { 304 chars = reinterpret_cast<const void**>(&d.s.u2.nonInlineCharsLatin1); 305 } 306 307 size_t bytesUsed = length() * sizeof(CharT); 308 size_t bytesCapacity = 309 isExtensible() ? (asExtensible().capacity() * sizeof(CharT)) : bytesUsed; 310 MOZ_ASSERT(bytesUsed <= bytesCapacity); 311 312 if (nursery->maybeMoveNurseryOrMallocBufferOnPromotion( 313 const_cast<void**>(chars), this, bytesUsed, bytesCapacity, 314 js::MemoryUse::StringContents, 315 js::StringBufferArena) == js::Nursery::BufferMoved) { 316 MOZ_ASSERT(allocSize() == bytesCapacity); 317 return bytesCapacity; 318 } 319 320 return 0; 321 } 322 323 inline size_t JSLinearString::allocSize() const { 324 MOZ_ASSERT(ownsMallocedChars() || hasStringBuffer()); 325 326 size_t charSize = 327 hasLatin1Chars() ? sizeof(JS::Latin1Char) : sizeof(char16_t); 328 size_t count = isExtensible() ? asExtensible().capacity() : length(); 329 return count * charSize; 330 } 331 332 inline size_t JSString::allocSize() const { 333 if (ownsMallocedChars() || hasStringBuffer()) { 334 return asLinear().allocSize(); 335 } 336 return 0; 337 } 338 339 inline JSRope::JSRope(JSString* left, JSString* right, size_t length) { 340 // JITs expect rope children aren't empty. 341 MOZ_ASSERT(!left->empty() && !right->empty()); 342 343 // |length| must be the sum of the length of both child nodes. 344 MOZ_ASSERT(left->length() + right->length() == length); 345 346 // |isLatin1| is set when both children are guaranteed to contain only Latin-1 347 // characters. Note that flattening either rope child can clear the Latin-1 348 // flag of that child, so it's possible that a Latin-1 rope can end up with 349 // both children being two-byte (dependent) strings. 350 bool isLatin1 = left->hasLatin1Chars() && right->hasLatin1Chars(); 351 352 // Do not try to make a rope that could fit inline. 353 MOZ_ASSERT_IF(!isLatin1, !JSInlineString::lengthFits<char16_t>(length)); 354 MOZ_ASSERT_IF(isLatin1, !JSInlineString::lengthFits<JS::Latin1Char>(length)); 355 356 if (isLatin1) { 357 setLengthAndFlags(length, INIT_ROPE_FLAGS | LATIN1_CHARS_BIT); 358 } else { 359 setLengthAndFlags(length, INIT_ROPE_FLAGS); 360 } 361 d.s.u2.left = left; 362 d.s.u3.right = right; 363 364 // Post-barrier by inserting into the whole cell buffer if either 365 // this -> left or this -> right is a tenured -> nursery edge. 366 if (isTenured()) { 367 js::gc::StoreBuffer* sb = left->storeBuffer(); 368 if (!sb) { 369 sb = right->storeBuffer(); 370 } 371 if (sb) { 372 sb->putWholeCell(this); 373 } 374 } 375 } 376 377 template <js::AllowGC allowGC> 378 MOZ_ALWAYS_INLINE JSRope* JSRope::new_( 379 JSContext* cx, 380 typename js::MaybeRooted<JSString*, allowGC>::HandleType left, 381 typename js::MaybeRooted<JSString*, allowGC>::HandleType right, 382 size_t length, js::gc::Heap heap) { 383 if (MOZ_UNLIKELY(!validateLengthInternal<allowGC>(cx, length))) { 384 return nullptr; 385 } 386 return cx->newCell<JSRope, allowGC>(heap, left, right, length); 387 } 388 389 inline JSDependentString::JSDependentString(JSLinearString* base, size_t start, 390 size_t length) { 391 MOZ_ASSERT(start + length <= base->length()); 392 JS::AutoCheckCannotGC nogc; 393 if (base->hasLatin1Chars()) { 394 setLengthAndFlags(length, INIT_DEPENDENT_FLAGS | LATIN1_CHARS_BIT); 395 d.s.u2.nonInlineCharsLatin1 = base->latin1Chars(nogc) + start; 396 } else { 397 setLengthAndFlags(length, INIT_DEPENDENT_FLAGS); 398 d.s.u2.nonInlineCharsTwoByte = base->twoByteChars(nogc) + start; 399 } 400 base->setDependedOn(); 401 d.s.u3.base = base; 402 if (isTenured() && !base->isTenured()) { 403 base->storeBuffer()->putWholeCell(this); 404 } 405 } 406 407 template <JS::ContractBaseChain contract> 408 MOZ_ALWAYS_INLINE JSLinearString* JSDependentString::newImpl_( 409 JSContext* cx, JSLinearString* baseArg, size_t start, size_t length, 410 js::gc::Heap heap) { 411 // Not passed in as a Handle because `base` is reassigned. 412 JS::Rooted<JSLinearString*> base(cx, baseArg); 413 414 // Do not try to make a dependent string that could fit inline. 415 MOZ_ASSERT_IF(base->hasTwoByteChars(), 416 !JSInlineString::lengthFits<char16_t>(length)); 417 MOZ_ASSERT_IF(!base->hasTwoByteChars(), 418 !JSInlineString::lengthFits<JS::Latin1Char>(length)); 419 420 // Invariant: if a tenured dependent string points to chars in the nursery, 421 // then the string must be in the store buffer. 422 // 423 // Refuse to create a chain tenured -> tenured -> nursery (with nursery 424 // chars). The same holds for anything else that might create length > 1 425 // chains of dependent strings. 426 bool mustContract; 427 if constexpr (contract == JS::ContractBaseChain::Contract) { 428 mustContract = true; 429 } else { 430 auto& nursery = cx->runtime()->gc.nursery(); 431 mustContract = nursery.isInside(base->nonInlineCharsRaw()); 432 } 433 434 if (mustContract) { 435 // Try to avoid long chains of dependent strings. We can't avoid these 436 // entirely, however, due to how ropes are flattened. 437 if (base->isDependent()) { 438 start += base->asDependent().baseOffset(); 439 base = base->asDependent().base(); 440 } 441 } 442 443 MOZ_ASSERT(start + length <= base->length()); 444 445 JSDependentString* str; 446 if constexpr (contract == JS::ContractBaseChain::Contract) { 447 return cx->newCell<JSDependentString>(heap, base, start, length); 448 } 449 450 str = cx->newCell<JSDependentString>(heap, base, start, length); 451 if (str && base->isDependent() && base->isTenured()) { 452 // Tenured dependent -> nursery base string edges are problematic for 453 // deduplication if the tenured dependent string can itself have strings 454 // dependent on it. Whenever such a thing can be created, the nursery base 455 // must be marked as non-deduplicatable. 456 JSString* rootBase = base; 457 while (rootBase->isDependent()) { 458 rootBase = rootBase->base(); 459 } 460 if (!rootBase->isTenured()) { 461 rootBase->setNonDeduplicatable(); 462 } 463 } 464 465 return str; 466 } 467 468 /* static */ 469 inline JSLinearString* JSDependentString::new_(JSContext* cx, 470 JSLinearString* base, 471 size_t start, size_t length, 472 js::gc::Heap heap) { 473 return newImpl_<JS::ContractBaseChain::Contract>(cx, base, start, length, 474 heap); 475 } 476 477 inline JSLinearString::JSLinearString(const char16_t* chars, size_t length, 478 bool hasBuffer) { 479 uint32_t flags = INIT_LINEAR_FLAGS | (hasBuffer ? HAS_STRING_BUFFER_BIT : 0); 480 setLengthAndFlags(length, flags); 481 // Check that the new buffer is located in the StringBufferArena. 482 checkStringCharsArena(chars, hasBuffer); 483 d.s.u2.nonInlineCharsTwoByte = chars; 484 } 485 486 inline JSLinearString::JSLinearString(const JS::Latin1Char* chars, 487 size_t length, bool hasBuffer) { 488 uint32_t flags = INIT_LINEAR_FLAGS | LATIN1_CHARS_BIT | 489 (hasBuffer ? HAS_STRING_BUFFER_BIT : 0); 490 setLengthAndFlags(length, flags); 491 // Check that the new buffer is located in the StringBufferArena. 492 checkStringCharsArena(chars, hasBuffer); 493 d.s.u2.nonInlineCharsLatin1 = chars; 494 } 495 496 template <typename CharT> 497 inline JSLinearString::JSLinearString( 498 JS::MutableHandle<JSString::OwnedChars<CharT>> chars) { 499 // Note that it is possible that the chars may have been moved from the 500 // nursery to the malloc heap when allocating the Cell that this constructor 501 // is initializing. 502 MOZ_ASSERT(chars.data()); 503 checkStringCharsArena(chars.data(), chars.hasStringBuffer()); 504 if (isTenured()) { 505 chars.ensureNonNursery(); 506 } 507 uint32_t flags = INIT_LINEAR_FLAGS; 508 if (chars.hasStringBuffer()) { 509 flags |= HAS_STRING_BUFFER_BIT; 510 } 511 if constexpr (std::is_same_v<CharT, char16_t>) { 512 setLengthAndFlags(chars.length(), flags); 513 d.s.u2.nonInlineCharsTwoByte = chars.data(); 514 } else { 515 setLengthAndFlags(chars.length(), flags | LATIN1_CHARS_BIT); 516 d.s.u2.nonInlineCharsLatin1 = chars.data(); 517 } 518 } 519 520 void JSLinearString::disownCharsBecauseError() { 521 setLengthAndFlags(0, INIT_LINEAR_FLAGS | LATIN1_CHARS_BIT); 522 d.s.u2.nonInlineCharsLatin1 = nullptr; 523 } 524 525 template <js::AllowGC allowGC, typename CharT> 526 MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::new_( 527 JSContext* cx, JS::MutableHandle<JSString::OwnedChars<CharT>> chars, 528 js::gc::Heap heap) { 529 if (MOZ_UNLIKELY(!validateLengthInternal<allowGC>(cx, chars.length()))) { 530 return nullptr; 531 } 532 533 return newValidLength<allowGC>(cx, chars, heap); 534 } 535 536 template <js::AllowGC allowGC, typename CharT> 537 MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::newValidLength( 538 JSContext* cx, JS::MutableHandle<JSString::OwnedChars<CharT>> chars, 539 js::gc::Heap heap) { 540 MOZ_ASSERT(!cx->zone()->isAtomsZone()); 541 MOZ_ASSERT(!JSInlineString::lengthFits<CharT>(chars.length())); 542 JSLinearString* str = cx->newCell<JSLinearString, allowGC>(heap, chars); 543 if (!str) { 544 return nullptr; 545 } 546 547 if (!str->isTenured()) { 548 // If the following registration fails, the string is partially initialized 549 // and must be made valid, or its finalizer may attempt to free 550 // uninitialized memory. 551 bool ok = true; 552 if (chars.isMalloced()) { 553 ok = cx->nursery().registerMallocedBuffer(chars.data(), chars.size()); 554 } else if (chars.hasStringBuffer()) { 555 ok = cx->nursery().addStringBuffer(str); 556 } 557 if (!ok) { 558 str->disownCharsBecauseError(); 559 if (allowGC) { 560 ReportOutOfMemory(cx); 561 } 562 return nullptr; 563 } 564 } else { 565 // Note: this will overcount if the same StringBuffer is used by multiple JS 566 // strings. Unfortunately we don't have a good way to avoid this. 567 cx->zone()->addCellMemory(str, chars.size(), js::MemoryUse::StringContents); 568 } 569 570 // Either the tenured Cell or the nursery's registry owns the chars now. 571 chars.release(); 572 573 return str; 574 } 575 576 template <typename CharT> 577 MOZ_ALWAYS_INLINE JSAtom* JSAtom::newValidLength(JSContext* cx, 578 OwnedChars<CharT>& chars, 579 js::HashNumber hash) { 580 size_t length = chars.length(); 581 MOZ_ASSERT(validateLength(cx, length)); 582 MOZ_ASSERT(cx->zone()->isAtomsZone()); 583 584 // Note: atom allocation can't GC. The unrooted |chars| argument relies on 585 // this. 586 JSAtom* str = cx->newCell<js::NormalAtom, js::NoGC>(chars, hash); 587 if (!str) { 588 return nullptr; 589 } 590 591 // The atom now owns the chars. 592 chars.release(); 593 594 MOZ_ASSERT(str->isTenured()); 595 cx->zone()->addCellMemory(str, length * sizeof(CharT), 596 js::MemoryUse::StringContents); 597 598 return str; 599 } 600 601 inline js::PropertyName* JSLinearString::toPropertyName(JSContext* cx) { 602 #ifdef DEBUG 603 uint32_t dummy; 604 MOZ_ASSERT(!isIndex(&dummy)); 605 #endif 606 JSAtom* atom = js::AtomizeString(cx, this); 607 if (!atom) { 608 return nullptr; 609 } 610 return atom->asPropertyName(); 611 } 612 613 // String characters are movable in the following cases: 614 // 615 // 1. Inline nursery strings (moved during promotion) 616 // 2. Nursery strings with nursery chars (moved during promotion) 617 // 3. Nursery strings that are deduplicated (moved during promotion) 618 // 4. Inline tenured strings (moved during compaction) 619 // 620 // This method does not consider #3, because if this method returns true and the 621 // caller does not want the characters to move, it can fix them in place by 622 // setting the nondeduplicatable bit. (If the bit were already taken into 623 // consideration, then the caller wouldn't know whether the movability is 624 // "fixable" or not. If it is *only* movable because of the lack of the bit 625 // being set, then it is fixable by setting the bit.) 626 bool JSLinearString::hasMovableChars() const { 627 const JSLinearString* topBase = this; 628 while (topBase->hasBase()) { 629 topBase = topBase->base(); 630 } 631 if (topBase->isInline()) { 632 return true; 633 } 634 if (topBase->isTenured()) { 635 return false; 636 } 637 return topBase->storeBuffer()->nursery().isInside( 638 topBase->nonInlineCharsRaw()); 639 } 640 641 template <js::AllowGC allowGC> 642 MOZ_ALWAYS_INLINE JSThinInlineString* JSThinInlineString::new_( 643 JSContext* cx, js::gc::Heap heap) { 644 MOZ_ASSERT(!cx->zone()->isAtomsZone()); 645 return cx->newCell<JSThinInlineString, allowGC>(heap); 646 } 647 648 template <js::AllowGC allowGC> 649 MOZ_ALWAYS_INLINE JSFatInlineString* JSFatInlineString::new_( 650 JSContext* cx, js::gc::Heap heap) { 651 MOZ_ASSERT(!cx->zone()->isAtomsZone()); 652 return cx->newCell<JSFatInlineString, allowGC>(heap); 653 } 654 655 inline JSThinInlineString::JSThinInlineString(size_t length, 656 JS::Latin1Char** chars) { 657 MOZ_ASSERT(lengthFits<JS::Latin1Char>(length)); 658 setLengthAndFlags(length, INIT_THIN_INLINE_FLAGS | LATIN1_CHARS_BIT); 659 *chars = d.inlineStorageLatin1; 660 } 661 662 inline JSThinInlineString::JSThinInlineString(size_t length, char16_t** chars) { 663 MOZ_ASSERT(lengthFits<char16_t>(length)); 664 setLengthAndFlags(length, INIT_THIN_INLINE_FLAGS); 665 *chars = d.inlineStorageTwoByte; 666 } 667 668 inline JSFatInlineString::JSFatInlineString(size_t length, 669 JS::Latin1Char** chars) { 670 MOZ_ASSERT(lengthFits<JS::Latin1Char>(length)); 671 setLengthAndFlags(length, INIT_FAT_INLINE_FLAGS | LATIN1_CHARS_BIT); 672 *chars = d.inlineStorageLatin1; 673 } 674 675 inline JSFatInlineString::JSFatInlineString(size_t length, char16_t** chars) { 676 MOZ_ASSERT(lengthFits<char16_t>(length)); 677 setLengthAndFlags(length, INIT_FAT_INLINE_FLAGS); 678 *chars = d.inlineStorageTwoByte; 679 } 680 681 inline JSExternalString::JSExternalString( 682 const char16_t* chars, size_t length, 683 const JSExternalStringCallbacks* callbacks) { 684 MOZ_ASSERT(callbacks); 685 setLengthAndFlags(length, EXTERNAL_FLAGS); 686 d.s.u2.nonInlineCharsTwoByte = chars; 687 d.s.u3.externalCallbacks = callbacks; 688 } 689 690 inline JSExternalString::JSExternalString( 691 const JS::Latin1Char* chars, size_t length, 692 const JSExternalStringCallbacks* callbacks) { 693 MOZ_ASSERT(callbacks); 694 setLengthAndFlags(length, EXTERNAL_FLAGS | LATIN1_CHARS_BIT); 695 d.s.u2.nonInlineCharsLatin1 = chars; 696 d.s.u3.externalCallbacks = callbacks; 697 } 698 699 template <typename CharT> 700 /* static */ 701 MOZ_ALWAYS_INLINE JSExternalString* JSExternalString::newImpl( 702 JSContext* cx, const CharT* chars, size_t length, 703 const JSExternalStringCallbacks* callbacks) { 704 if (MOZ_UNLIKELY(!validateLength(cx, length))) { 705 return nullptr; 706 } 707 auto* str = cx->newCell<JSExternalString>(chars, length, callbacks); 708 709 if (!str) { 710 return nullptr; 711 } 712 size_t nbytes = length * sizeof(CharT); 713 714 MOZ_ASSERT(str->isTenured()); 715 js::AddCellMemory(str, nbytes, js::MemoryUse::StringContents); 716 717 return str; 718 } 719 720 /* static */ 721 MOZ_ALWAYS_INLINE JSExternalString* JSExternalString::new_( 722 JSContext* cx, const JS::Latin1Char* chars, size_t length, 723 const JSExternalStringCallbacks* callbacks) { 724 return newImpl(cx, chars, length, callbacks); 725 } 726 727 /* static */ 728 MOZ_ALWAYS_INLINE JSExternalString* JSExternalString::new_( 729 JSContext* cx, const char16_t* chars, size_t length, 730 const JSExternalStringCallbacks* callbacks) { 731 return newImpl(cx, chars, length, callbacks); 732 } 733 734 template <typename CharT> 735 inline js::NormalAtom::NormalAtom(const OwnedChars<CharT>& chars, 736 js::HashNumber hash) 737 : hash_(hash) { 738 // Check that the new buffer is located in the StringBufferArena 739 checkStringCharsArena(chars.data(), chars.hasStringBuffer()); 740 741 uint32_t flags = INIT_LINEAR_FLAGS | ATOM_BIT; 742 if (chars.hasStringBuffer()) { 743 flags |= HAS_STRING_BUFFER_BIT; 744 } 745 746 if constexpr (std::is_same_v<CharT, char16_t>) { 747 setLengthAndFlags(chars.length(), flags); 748 d.s.u2.nonInlineCharsTwoByte = chars.data(); 749 } else { 750 setLengthAndFlags(chars.length(), flags | LATIN1_CHARS_BIT); 751 d.s.u2.nonInlineCharsLatin1 = chars.data(); 752 } 753 } 754 755 #ifndef JS_64BIT 756 inline js::ThinInlineAtom::ThinInlineAtom(size_t length, JS::Latin1Char** chars, 757 js::HashNumber hash) 758 : NormalAtom(hash) { 759 setLengthAndFlags(length, 760 INIT_THIN_INLINE_FLAGS | LATIN1_CHARS_BIT | ATOM_BIT); 761 *chars = d.inlineStorageLatin1; 762 } 763 764 inline js::ThinInlineAtom::ThinInlineAtom(size_t length, char16_t** chars, 765 js::HashNumber hash) 766 : NormalAtom(hash) { 767 setLengthAndFlags(length, INIT_THIN_INLINE_FLAGS | ATOM_BIT); 768 *chars = d.inlineStorageTwoByte; 769 } 770 #endif 771 772 inline js::FatInlineAtom::FatInlineAtom(size_t length, JS::Latin1Char** chars, 773 js::HashNumber hash) 774 : hash_(hash) { 775 MOZ_ASSERT(lengthFits<JS::Latin1Char>(length)); 776 setLengthAndFlags(length, 777 INIT_FAT_INLINE_FLAGS | LATIN1_CHARS_BIT | ATOM_BIT); 778 *chars = d.inlineStorageLatin1; 779 } 780 781 inline js::FatInlineAtom::FatInlineAtom(size_t length, char16_t** chars, 782 js::HashNumber hash) 783 : hash_(hash) { 784 MOZ_ASSERT(lengthFits<char16_t>(length)); 785 setLengthAndFlags(length, INIT_FAT_INLINE_FLAGS | ATOM_BIT); 786 *chars = d.inlineStorageTwoByte; 787 } 788 789 inline JSLinearString* js::StaticStrings::getUnitString(JSContext* cx, 790 char16_t c) { 791 if (c < UNIT_STATIC_LIMIT) { 792 return getUnit(c); 793 } 794 return js::NewInlineString<CanGC>(cx, {c}, 1); 795 } 796 797 inline JSLinearString* js::StaticStrings::getUnitStringForElement( 798 JSContext* cx, JSString* str, size_t index) { 799 MOZ_ASSERT(index < str->length()); 800 801 char16_t c; 802 if (!str->getChar(cx, index, &c)) { 803 return nullptr; 804 } 805 return getUnitString(cx, c); 806 } 807 808 inline JSLinearString* js::StaticStrings::getUnitStringForElement( 809 JSContext* cx, const JSLinearString* str, size_t index) { 810 MOZ_ASSERT(index < str->length()); 811 812 char16_t c = str->latin1OrTwoByteChar(index); 813 return getUnitString(cx, c); 814 } 815 816 MOZ_ALWAYS_INLINE void JSString::finalize(JS::GCContext* gcx) { 817 /* FatInline strings are in a different arena. */ 818 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING); 819 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM); 820 821 if (isLinear()) { 822 asLinear().finalize(gcx); 823 } else { 824 MOZ_ASSERT(isRope()); 825 } 826 } 827 828 inline void JSLinearString::finalize(JS::GCContext* gcx) { 829 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_STRING); 830 MOZ_ASSERT(getAllocKind() != js::gc::AllocKind::FAT_INLINE_ATOM); 831 832 if (!isInline() && !isDependent()) { 833 size_t size = allocSize(); 834 if (hasStringBuffer()) { 835 mozilla::StringBuffer* buffer = stringBuffer(); 836 buffer->Release(); 837 gcx->removeCellMemory(this, size, js::MemoryUse::StringContents); 838 } else { 839 gcx->free_(this, nonInlineCharsRaw(), size, 840 js::MemoryUse::StringContents); 841 } 842 } 843 } 844 845 inline void JSFatInlineString::finalize(JS::GCContext* gcx) { 846 MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_STRING); 847 MOZ_ASSERT(isInline()); 848 849 // Nothing to do. 850 } 851 852 inline void js::FatInlineAtom::finalize(JS::GCContext* gcx) { 853 MOZ_ASSERT(JSString::isAtom()); 854 MOZ_ASSERT(getAllocKind() == js::gc::AllocKind::FAT_INLINE_ATOM); 855 856 // Nothing to do. 857 } 858 859 inline void JSExternalString::finalize(JS::GCContext* gcx) { 860 MOZ_ASSERT(JSString::isExternal()); 861 862 if (hasLatin1Chars()) { 863 size_t nbytes = length() * sizeof(JS::Latin1Char); 864 gcx->removeCellMemory(this, nbytes, js::MemoryUse::StringContents); 865 866 callbacks()->finalize(const_cast<JS::Latin1Char*>(rawLatin1Chars())); 867 } else { 868 size_t nbytes = length() * sizeof(char16_t); 869 gcx->removeCellMemory(this, nbytes, js::MemoryUse::StringContents); 870 871 callbacks()->finalize(const_cast<char16_t*>(rawTwoByteChars())); 872 } 873 } 874 875 #endif /* vm_StringType_inl_h */