StringType.cpp (108300B)
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 #include "vm/StringType-inl.h" 8 9 #include "mozilla/DebugOnly.h" 10 #include "mozilla/HashFunctions.h" 11 #include "mozilla/Latin1.h" 12 #include "mozilla/MemoryReporting.h" 13 #include "mozilla/PodOperations.h" 14 #include "mozilla/RangedPtr.h" 15 #include "mozilla/StringBuffer.h" 16 #include "mozilla/TextUtils.h" 17 #include "mozilla/Utf8.h" 18 #include "mozilla/Vector.h" 19 20 #include <algorithm> // std::{all_of,copy_n,enable_if,is_const,move} 21 #include <type_traits> // std::is_same, std::is_unsigned 22 23 #include "jsfriendapi.h" 24 #include "jsnum.h" 25 26 #include "builtin/Boolean.h" 27 #include "gc/AllocKind.h" 28 #include "gc/MaybeRooted.h" 29 #include "gc/Nursery.h" 30 #include "js/CharacterEncoding.h" 31 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 32 #include "js/Printer.h" // js::GenericPrinter 33 #include "js/PropertyAndElement.h" // JS_DefineElement 34 #include "js/SourceText.h" // JS::SourceText 35 #include "js/StableStringChars.h" 36 #include "js/UbiNode.h" 37 #include "util/Identifier.h" // js::IsIdentifierNameOrPrivateName 38 #include "util/Unicode.h" 39 #include "vm/GeckoProfiler.h" 40 #include "vm/JSONPrinter.h" // js::JSONPrinter 41 #include "vm/StaticStrings.h" 42 #include "vm/ToSource.h" // js::ValueToSource 43 44 #include "gc/Marking-inl.h" 45 #include "vm/GeckoProfiler-inl.h" 46 47 using namespace js; 48 49 using mozilla::AsWritableChars; 50 using mozilla::ConvertLatin1toUtf16; 51 using mozilla::IsAsciiDigit; 52 using mozilla::IsUtf16Latin1; 53 using mozilla::LossyConvertUtf16toLatin1; 54 using mozilla::PodCopy; 55 using mozilla::RangedPtr; 56 using mozilla::RoundUpPow2; 57 using mozilla::Span; 58 59 using JS::AutoCheckCannotGC; 60 using JS::AutoStableStringChars; 61 62 using UniqueLatin1Chars = UniquePtr<Latin1Char[], JS::FreePolicy>; 63 64 size_t JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 65 // JSRope: do nothing, we'll count all children chars when we hit the leaf 66 // strings. 67 if (isRope()) { 68 return 0; 69 } 70 71 MOZ_ASSERT(isLinear()); 72 73 // JSDependentString: do nothing, we'll count the chars when we hit the base 74 // string. 75 if (isDependent()) { 76 return 0; 77 } 78 79 // JSExternalString: Ask the embedding to tell us what's going on. 80 if (isExternal()) { 81 // Our callback isn't supposed to cause GC. 82 JS::AutoSuppressGCAnalysis nogc; 83 JSExternalString& external = asExternal(); 84 if (external.hasLatin1Chars()) { 85 return asExternal().callbacks()->sizeOfBuffer(external.latin1Chars(), 86 mallocSizeOf); 87 } else { 88 return asExternal().callbacks()->sizeOfBuffer(external.twoByteChars(), 89 mallocSizeOf); 90 } 91 } 92 93 // JSInlineString, JSFatInlineString, js::ThinInlineAtom, js::FatInlineAtom: 94 // the chars are inline. 95 if (isInline()) { 96 return 0; 97 } 98 99 JSLinearString& linear = asLinear(); 100 101 if (hasStringBuffer()) { 102 return linear.stringBuffer()->SizeOfIncludingThisIfUnshared(mallocSizeOf); 103 } 104 105 // Chars in the nursery are owned by the nursery. 106 if (!ownsMallocedChars()) { 107 return 0; 108 } 109 110 // Everything else: measure the space for the chars. 111 return linear.hasLatin1Chars() ? mallocSizeOf(linear.rawLatin1Chars()) 112 : mallocSizeOf(linear.rawTwoByteChars()); 113 } 114 115 JS::ubi::Node::Size JS::ubi::Concrete<JSString>::size( 116 mozilla::MallocSizeOf mallocSizeOf) const { 117 JSString& str = get(); 118 size_t size; 119 if (str.isAtom()) { 120 if (str.isInline()) { 121 size = str.isFatInline() ? sizeof(js::FatInlineAtom) 122 : sizeof(js::ThinInlineAtom); 123 } else { 124 size = sizeof(js::NormalAtom); 125 } 126 } else { 127 size = str.isFatInline() ? sizeof(JSFatInlineString) : sizeof(JSString); 128 } 129 130 if (IsInsideNursery(&str)) { 131 size += Nursery::nurseryCellHeaderSize(); 132 } 133 134 size += str.sizeOfExcludingThis(mallocSizeOf); 135 136 return size; 137 } 138 139 const char16_t JS::ubi::Concrete<JSString>::concreteTypeName[] = u"JSString"; 140 141 mozilla::Maybe<std::tuple<size_t, size_t>> JSString::encodeUTF8Partial( 142 const JS::AutoRequireNoGC& nogc, mozilla::Span<char> buffer) const { 143 mozilla::Vector<const JSString*, 16, SystemAllocPolicy> stack; 144 const JSString* current = this; 145 char16_t pendingLeadSurrogate = 0; // U+0000 means no pending lead surrogate 146 size_t totalRead = 0; 147 size_t totalWritten = 0; 148 for (;;) { 149 if (current->isRope()) { 150 JSRope& rope = current->asRope(); 151 if (!stack.append(rope.rightChild())) { 152 // OOM 153 return mozilla::Nothing(); 154 } 155 current = rope.leftChild(); 156 continue; 157 } 158 159 JSLinearString& linear = current->asLinear(); 160 if (MOZ_LIKELY(linear.hasLatin1Chars())) { 161 if (MOZ_UNLIKELY(pendingLeadSurrogate)) { 162 if (buffer.Length() < 3) { 163 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 164 } 165 buffer[0] = '\xEF'; 166 buffer[1] = '\xBF'; 167 buffer[2] = '\xBD'; 168 buffer = buffer.From(3); 169 totalRead += 1; // pendingLeadSurrogate 170 totalWritten += 3; 171 pendingLeadSurrogate = 0; 172 } 173 auto src = mozilla::AsChars( 174 mozilla::Span(linear.latin1Chars(nogc), linear.length())); 175 size_t read; 176 size_t written; 177 std::tie(read, written) = 178 mozilla::ConvertLatin1toUtf8Partial(src, buffer); 179 buffer = buffer.From(written); 180 totalRead += read; 181 totalWritten += written; 182 if (read < src.Length()) { 183 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 184 } 185 } else { 186 auto src = mozilla::Span(linear.twoByteChars(nogc), linear.length()); 187 if (MOZ_UNLIKELY(pendingLeadSurrogate)) { 188 char16_t first = 0; 189 if (!src.IsEmpty()) { 190 first = src[0]; 191 } 192 if (unicode::IsTrailSurrogate(first)) { 193 // Got a surrogate pair 194 if (buffer.Length() < 4) { 195 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 196 } 197 uint32_t astral = unicode::UTF16Decode(pendingLeadSurrogate, first); 198 buffer[0] = char(0b1111'0000 | (astral >> 18)); 199 buffer[1] = char(0b1000'0000 | ((astral >> 12) & 0b11'1111)); 200 buffer[2] = char(0b1000'0000 | ((astral >> 6) & 0b11'1111)); 201 buffer[3] = char(0b1000'0000 | (astral & 0b11'1111)); 202 src = src.From(1); 203 buffer = buffer.From(4); 204 totalRead += 2; // both pendingLeadSurrogate and first! 205 totalWritten += 4; 206 } else { 207 // unpaired surrogate 208 if (buffer.Length() < 3) { 209 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 210 } 211 buffer[0] = '\xEF'; 212 buffer[1] = '\xBF'; 213 buffer[2] = '\xBD'; 214 buffer = buffer.From(3); 215 totalRead += 1; // pendingLeadSurrogate 216 totalWritten += 3; 217 } 218 pendingLeadSurrogate = 0; 219 } 220 if (!src.IsEmpty()) { 221 char16_t last = src[src.Length() - 1]; 222 if (unicode::IsLeadSurrogate(last)) { 223 src = src.To(src.Length() - 1); 224 pendingLeadSurrogate = last; 225 } else { 226 MOZ_ASSERT(!pendingLeadSurrogate); 227 } 228 size_t read; 229 size_t written; 230 std::tie(read, written) = 231 mozilla::ConvertUtf16toUtf8Partial(src, buffer); 232 buffer = buffer.From(written); 233 totalRead += read; 234 totalWritten += written; 235 if (read < src.Length()) { 236 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 237 } 238 } 239 } 240 if (stack.empty()) { 241 break; 242 } 243 current = stack.popCopy(); 244 } 245 if (MOZ_UNLIKELY(pendingLeadSurrogate)) { 246 if (buffer.Length() < 3) { 247 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 248 } 249 buffer[0] = '\xEF'; 250 buffer[1] = '\xBF'; 251 buffer[2] = '\xBD'; 252 // No need to update buffer and pendingLeadSurrogate anymore 253 totalRead += 1; 254 totalWritten += 3; 255 } 256 return mozilla::Some(std::make_tuple(totalRead, totalWritten)); 257 } 258 259 #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) 260 template <typename CharT> 261 /*static */ 262 void JSString::dumpCharsNoQuote(const CharT* s, size_t n, 263 js::GenericPrinter& out) { 264 for (size_t i = 0; i < n; i++) { 265 char16_t c = s[i]; 266 if (c == '"') { 267 out.put("\\\""); 268 } else if (c == '\'') { 269 out.put("\\'"); 270 } else if (c == '`') { 271 out.put("\\`"); 272 } else if (c == '\\') { 273 out.put("\\\\"); 274 } else if (c == '\r') { 275 out.put("\\r"); 276 } else if (c == '\n') { 277 out.put("\\n"); 278 } else if (c == '\t') { 279 out.put("\\t"); 280 } else if (c >= 32 && c < 127) { 281 out.putChar((char)s[i]); 282 } else if (c <= 255) { 283 out.printf("\\x%02x", unsigned(c)); 284 } else { 285 out.printf("\\u%04x", unsigned(c)); 286 } 287 } 288 } 289 290 /* static */ 291 template void JSString::dumpCharsNoQuote(const Latin1Char* s, size_t n, 292 js::GenericPrinter& out); 293 294 /* static */ 295 template void JSString::dumpCharsNoQuote(const char16_t* s, size_t n, 296 js::GenericPrinter& out); 297 298 void JSString::dump() const { 299 js::Fprinter out(stderr); 300 dump(out); 301 } 302 303 void JSString::dump(js::GenericPrinter& out) const { 304 js::JSONPrinter json(out); 305 dump(json); 306 out.put("\n"); 307 } 308 309 void JSString::dump(js::JSONPrinter& json) const { 310 json.beginObject(); 311 dumpFields(json); 312 json.endObject(); 313 } 314 315 const char* RepresentationToString(const JSString* s) { 316 if (s->isAtom()) { 317 return "JSAtom"; 318 } 319 320 if (s->isLinear()) { 321 if (s->isDependent()) { 322 return "JSDependentString"; 323 } 324 if (s->isExternal()) { 325 return "JSExternalString"; 326 } 327 if (s->isExtensible()) { 328 return "JSExtensibleString"; 329 } 330 331 if (s->isInline()) { 332 if (s->isFatInline()) { 333 return "JSFatInlineString"; 334 } 335 return "JSThinInlineString"; 336 } 337 338 return "JSLinearString"; 339 } 340 341 if (s->isRope()) { 342 return "JSRope"; 343 } 344 345 return "JSString"; 346 } 347 348 template <typename KnownF, typename UnknownF> 349 void ForEachStringFlag(const JSString* str, uint32_t flags, KnownF known, 350 UnknownF unknown) { 351 for (uint32_t i = js::Bit(3); i < js::Bit(17); i = i << 1) { 352 if (!(flags & i)) { 353 continue; 354 } 355 switch (i) { 356 case JSString::ATOM_BIT: 357 known("ATOM_BIT"); 358 break; 359 case JSString::LINEAR_BIT: 360 known("LINEAR_BIT"); 361 break; 362 case JSString::DEPENDENT_BIT: 363 known("DEPENDENT_BIT"); 364 break; 365 case JSString::INLINE_CHARS_BIT: 366 known("INLINE_BIT"); 367 break; 368 case JSString::LINEAR_IS_EXTENSIBLE_BIT: 369 static_assert(JSString::LINEAR_IS_EXTENSIBLE_BIT == 370 JSString::INLINE_IS_FAT_BIT); 371 if (str->isLinear()) { 372 if (str->isInline()) { 373 known("FAT"); 374 } else if (!str->isAtom()) { 375 known("EXTENSIBLE"); 376 } else { 377 unknown(i); 378 } 379 } else { 380 unknown(i); 381 } 382 break; 383 case JSString::LINEAR_IS_EXTERNAL_BIT: 384 static_assert(JSString::LINEAR_IS_EXTERNAL_BIT == 385 JSString::ATOM_IS_PERMANENT_BIT); 386 if (str->isAtom()) { 387 known("PERMANENT"); 388 } else if (str->isLinear()) { 389 known("EXTERNAL"); 390 } else { 391 unknown(i); 392 } 393 break; 394 case JSString::LATIN1_CHARS_BIT: 395 known("LATIN1_CHARS_BIT"); 396 break; 397 case JSString::HAS_STRING_BUFFER_BIT: 398 known("HAS_STRING_BUFFER_BIT"); 399 break; 400 case JSString::ATOM_IS_INDEX_BIT: 401 if (str->isAtom()) { 402 known("ATOM_IS_INDEX_BIT"); 403 } else { 404 known("ATOM_REF_BIT"); 405 } 406 break; 407 case JSString::INDEX_VALUE_BIT: 408 known("INDEX_VALUE_BIT"); 409 break; 410 case JSString::IN_STRING_TO_ATOM_CACHE: 411 known("IN_STRING_TO_ATOM_CACHE"); 412 break; 413 case JSString::FLATTEN_VISIT_RIGHT: 414 if (str->isRope()) { 415 known("FLATTEN_VISIT_RIGHT"); 416 } else { 417 known("DEPENDED_ON_BIT"); 418 } 419 break; 420 case JSString::FLATTEN_FINISH_NODE: 421 static_assert(JSString::FLATTEN_FINISH_NODE == 422 JSString::PINNED_ATOM_BIT); 423 if (str->isRope()) { 424 known("FLATTEN_FINISH_NODE"); 425 } else if (str->isAtom()) { 426 known("PINNED_ATOM_BIT"); 427 } else { 428 known("NON_DEDUP_BIT"); 429 } 430 break; 431 default: 432 unknown(i); 433 break; 434 } 435 } 436 } 437 438 void JSString::dumpFields(js::JSONPrinter& json) const { 439 dumpCommonFields(json); 440 dumpCharsFields(json); 441 } 442 443 void JSString::dumpCommonFields(js::JSONPrinter& json) const { 444 json.formatProperty("address", "(%s*)0x%p", RepresentationToString(this), 445 this); 446 447 json.beginInlineListProperty("flags"); 448 ForEachStringFlag( 449 this, flags(), [&](const char* name) { json.value("%s", name); }, 450 [&](uint32_t value) { json.value("Unknown(%08x)", value); }); 451 json.endInlineList(); 452 453 if (hasIndexValue()) { 454 json.property("indexValue", getIndexValue()); 455 } 456 457 json.boolProperty("isTenured", isTenured()); 458 459 json.property("length", length()); 460 } 461 462 void JSString::dumpCharsFields(js::JSONPrinter& json) const { 463 if (isLinear()) { 464 const JSLinearString* linear = &asLinear(); 465 466 AutoCheckCannotGC nogc; 467 if (hasLatin1Chars()) { 468 const Latin1Char* chars = linear->latin1Chars(nogc); 469 470 json.formatProperty("chars", "(JS::Latin1Char*)0x%p", chars); 471 472 js::GenericPrinter& out = json.beginStringProperty("value"); 473 dumpCharsNoQuote(chars, length(), out); 474 json.endStringProperty(); 475 } else { 476 const char16_t* chars = linear->twoByteChars(nogc); 477 478 json.formatProperty("chars", "(char16_t*)0x%p", chars); 479 480 js::GenericPrinter& out = json.beginStringProperty("value"); 481 dumpCharsNoQuote(chars, length(), out); 482 json.endStringProperty(); 483 } 484 } else { 485 js::GenericPrinter& out = json.beginStringProperty("value"); 486 dumpCharsNoQuote(out); 487 json.endStringProperty(); 488 } 489 } 490 491 void JSString::dumpRepresentation() const { 492 js::Fprinter out(stderr); 493 dumpRepresentation(out); 494 } 495 496 void JSString::dumpRepresentation(js::GenericPrinter& out) const { 497 js::JSONPrinter json(out); 498 dumpRepresentation(json); 499 out.put("\n"); 500 } 501 502 void JSString::dumpRepresentation(js::JSONPrinter& json) const { 503 json.beginObject(); 504 dumpRepresentationFields(json); 505 json.endObject(); 506 } 507 508 void JSString::dumpRepresentationFields(js::JSONPrinter& json) const { 509 dumpCommonFields(json); 510 511 if (isAtom()) { 512 asAtom().dumpOwnRepresentationFields(json); 513 } else if (isLinear()) { 514 asLinear().dumpOwnRepresentationFields(json); 515 516 if (isDependent()) { 517 asDependent().dumpOwnRepresentationFields(json); 518 } else if (isExternal()) { 519 asExternal().dumpOwnRepresentationFields(json); 520 } else if (isExtensible()) { 521 asExtensible().dumpOwnRepresentationFields(json); 522 } else if (isInline()) { 523 asInline().dumpOwnRepresentationFields(json); 524 } 525 } else if (isRope()) { 526 asRope().dumpOwnRepresentationFields(json); 527 // Rope already shows the chars. 528 return; 529 } 530 531 dumpCharsFields(json); 532 } 533 534 void JSString::dumpStringContent(js::GenericPrinter& out) const { 535 dumpCharsSingleQuote(out); 536 537 out.printf(" @ (%s*)0x%p", RepresentationToString(this), this); 538 } 539 540 void JSString::dumpPropertyName(js::GenericPrinter& out) const { 541 dumpCharsNoQuote(out); 542 } 543 544 void JSString::dumpChars(js::GenericPrinter& out) const { 545 out.putChar('"'); 546 dumpCharsNoQuote(out); 547 out.putChar('"'); 548 } 549 550 void JSString::dumpCharsSingleQuote(js::GenericPrinter& out) const { 551 out.putChar('\''); 552 dumpCharsNoQuote(out); 553 out.putChar('\''); 554 } 555 556 void JSString::dumpCharsNoQuote(js::GenericPrinter& out) const { 557 if (isLinear()) { 558 const JSLinearString* linear = &asLinear(); 559 560 AutoCheckCannotGC nogc; 561 if (hasLatin1Chars()) { 562 dumpCharsNoQuote(linear->latin1Chars(nogc), length(), out); 563 } else { 564 dumpCharsNoQuote(linear->twoByteChars(nogc), length(), out); 565 } 566 } else if (isRope()) { 567 JSRope* rope = &asRope(); 568 rope->leftChild()->dumpCharsNoQuote(out); 569 rope->rightChild()->dumpCharsNoQuote(out); 570 } 571 } 572 573 bool JSString::equals(const char* s) { 574 JSLinearString* linear = ensureLinear(nullptr); 575 if (!linear) { 576 // This is DEBUG-only code. 577 fprintf(stderr, "OOM in JSString::equals!\n"); 578 return false; 579 } 580 581 return StringEqualsAscii(linear, s); 582 } 583 #endif /* defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) */ 584 585 JSExtensibleString& JSLinearString::makeExtensible(size_t capacity) { 586 MOZ_ASSERT(!isDependent()); 587 MOZ_ASSERT(!isInline()); 588 MOZ_ASSERT(!isAtom()); 589 MOZ_ASSERT(!isExternal()); 590 MOZ_ASSERT(capacity >= length()); 591 size_t oldSize = allocSize(); 592 js::RemoveCellMemory(this, oldSize, js::MemoryUse::StringContents); 593 setLengthAndFlags(length(), flags() | EXTENSIBLE_FLAGS); 594 d.s.u3.capacity = capacity; 595 size_t newSize = allocSize(); 596 js::AddCellMemory(this, newSize, js::MemoryUse::StringContents); 597 MOZ_ASSERT(newSize >= oldSize); 598 if (!isTenured() && newSize > oldSize) { 599 auto& nursery = runtimeFromMainThread()->gc.nursery(); 600 nursery.addMallocedBufferBytes(newSize - oldSize); 601 } 602 return asExtensible(); 603 } 604 605 template <typename CharT> 606 static MOZ_ALWAYS_INLINE bool AllocCharsForFlatten(Nursery& nursery, 607 JSString* str, size_t length, 608 CharT** chars, 609 size_t* capacity, 610 bool* hasStringBuffer) { 611 /* 612 * Grow by 12.5% if the buffer is very large. Otherwise, round up to the 613 * next power of 2. This is similar to what we do with object elements; see 614 * NativeObject::goodElementsAllocationAmount. 615 */ 616 auto calcCapacity = [](size_t length, size_t maxCapacity) { 617 static const size_t DOUBLING_MAX = 1024 * 1024; 618 if (length > DOUBLING_MAX) { 619 return std::min<size_t>(maxCapacity, length + (length / 8)); 620 } 621 size_t capacity = RoundUpPow2(length); 622 MOZ_ASSERT(capacity <= maxCapacity); 623 return capacity; 624 }; 625 626 if (length < JSString::MIN_BYTES_FOR_BUFFER / sizeof(CharT)) { 627 *capacity = calcCapacity(length, JSString::MAX_LENGTH); 628 MOZ_ASSERT(length <= *capacity); 629 MOZ_ASSERT(*capacity <= JSString::MAX_LENGTH); 630 631 size_t allocSize = *capacity * sizeof(CharT); 632 void* buffer = nursery.allocNurseryOrMallocBuffer( 633 str->zone(), str, allocSize, js::StringBufferArena); 634 if (!buffer) { 635 return false; 636 } 637 638 *chars = static_cast<CharT*>(buffer); 639 *hasStringBuffer = false; 640 return true; 641 } 642 643 using mozilla::StringBuffer; 644 645 static_assert(StringBuffer::IsValidLength<CharT>(JSString::MAX_LENGTH), 646 "JSString length must be valid for StringBuffer"); 647 648 // Include extra space for the header and the null-terminator before 649 // calculating the capacity. This ensures we make good use of jemalloc's 650 // bucket sizes. For example, for a Latin1 string with length 2000 we want to 651 // get a capacity of 2039 (chars). With the StringBuffer header (8 bytes) and 652 // the null-terminator this results in an allocation of 2048 bytes. 653 // 654 // Note: the null-terminator will not be included in the extensible string's 655 // capacity field. 656 static_assert(sizeof(StringBuffer) % sizeof(CharT) == 0); 657 static constexpr size_t ExtraChars = sizeof(StringBuffer) / sizeof(CharT) + 1; 658 659 size_t fullCapacity = 660 calcCapacity(length + ExtraChars, JSString::MAX_LENGTH + ExtraChars); 661 *capacity = fullCapacity - ExtraChars; 662 MOZ_ASSERT(length <= *capacity); 663 MOZ_ASSERT(*capacity <= JSString::MAX_LENGTH); 664 665 RefPtr<StringBuffer> buffer = StringBuffer::Alloc( 666 (*capacity + 1) * sizeof(CharT), mozilla::Some(js::StringBufferArena)); 667 if (!buffer) { 668 return false; 669 } 670 if (!str->isTenured()) { 671 auto* linear = static_cast<JSLinearString*>(str); // True when we're done. 672 if (!nursery.addExtensibleStringBuffer(linear, buffer)) { 673 return false; 674 } 675 } 676 // Transfer ownership to the caller, where the buffer will be used for the 677 // extensible string. 678 // Note: the null-terminator will be stored in flattenInternal. 679 StringBuffer* buf; 680 buffer.forget(&buf); 681 *chars = static_cast<CharT*>(buf->Data()); 682 *hasStringBuffer = true; 683 return true; 684 } 685 686 UniqueLatin1Chars JSRope::copyLatin1Chars(JSContext* maybecx, 687 arena_id_t destArenaId) const { 688 return copyCharsInternal<Latin1Char>(maybecx, destArenaId); 689 } 690 691 UniqueTwoByteChars JSRope::copyTwoByteChars(JSContext* maybecx, 692 arena_id_t destArenaId) const { 693 return copyCharsInternal<char16_t>(maybecx, destArenaId); 694 } 695 696 // Allocate chars for a string. If parameters and conditions allow, this will 697 // try to allocate in the nursery, but this may always fall back to a malloc 698 // allocation. The return value will record where the allocation happened. 699 template <typename CharT> 700 static MOZ_ALWAYS_INLINE JSString::OwnedChars<CharT> AllocChars(JSContext* cx, 701 size_t length, 702 gc::Heap heap) { 703 if (heap == gc::Heap::Default && cx->zone()->allocNurseryStrings()) { 704 MOZ_ASSERT(cx->nursery().isEnabled()); 705 void* buffer = cx->nursery().tryAllocateNurseryBuffer( 706 cx->zone(), length * sizeof(CharT), js::StringBufferArena); 707 if (buffer) { 708 using Kind = typename JSString::OwnedChars<CharT>::Kind; 709 return {static_cast<CharT*>(buffer), length, Kind::Nursery}; 710 } 711 } 712 713 static_assert(JSString::MIN_BYTES_FOR_BUFFER % sizeof(CharT) == 0); 714 715 if (length < JSString::MIN_BYTES_FOR_BUFFER / sizeof(CharT)) { 716 auto buffer = 717 cx->make_pod_arena_array<CharT>(js::StringBufferArena, length); 718 if (!buffer) { 719 return {}; 720 } 721 return {std::move(buffer), length}; 722 } 723 724 if (MOZ_UNLIKELY(!mozilla::StringBuffer::IsValidLength<CharT>(length))) { 725 ReportOversizedAllocation(cx, JSMSG_ALLOC_OVERFLOW); 726 return {}; 727 } 728 729 // Note: StringBuffers must be null-terminated. 730 RefPtr<mozilla::StringBuffer> buffer = mozilla::StringBuffer::Alloc( 731 (length + 1) * sizeof(CharT), mozilla::Some(js::StringBufferArena)); 732 if (!buffer) { 733 ReportOutOfMemory(cx); 734 return {}; 735 } 736 static_cast<CharT*>(buffer->Data())[length] = '\0'; 737 return {std::move(buffer), length}; 738 } 739 740 // Like AllocChars but for atom characters. Does not report an exception on OOM. 741 template <typename CharT> 742 JSString::OwnedChars<CharT> js::AllocAtomCharsValidLength(JSContext* cx, 743 size_t length) { 744 MOZ_ASSERT(cx->zone()->isAtomsZone()); 745 MOZ_ASSERT(JSAtom::validateLength(cx, length)); 746 MOZ_ASSERT(mozilla::StringBuffer::IsValidLength<CharT>(length)); 747 748 static_assert(JSString::MIN_BYTES_FOR_BUFFER % sizeof(CharT) == 0); 749 750 if (length < JSString::MIN_BYTES_FOR_BUFFER / sizeof(CharT)) { 751 auto buffer = 752 cx->make_pod_arena_array<CharT>(js::StringBufferArena, length); 753 if (!buffer) { 754 cx->recoverFromOutOfMemory(); 755 return {}; 756 } 757 return {std::move(buffer), length}; 758 } 759 760 // Note: StringBuffers must be null-terminated. 761 RefPtr<mozilla::StringBuffer> buffer = mozilla::StringBuffer::Alloc( 762 (length + 1) * sizeof(CharT), mozilla::Some(js::StringBufferArena)); 763 if (!buffer) { 764 return {}; 765 } 766 static_cast<CharT*>(buffer->Data())[length] = '\0'; 767 return {std::move(buffer), length}; 768 } 769 770 template JSString::OwnedChars<Latin1Char> js::AllocAtomCharsValidLength( 771 JSContext* cx, size_t length); 772 template JSString::OwnedChars<char16_t> js::AllocAtomCharsValidLength( 773 JSContext* cx, size_t length); 774 775 template <typename CharT> 776 UniquePtr<CharT[], JS::FreePolicy> JSRope::copyCharsInternal( 777 JSContext* maybecx, arena_id_t destArenaId) const { 778 // Left-leaning ropes are far more common than right-leaning ropes, so 779 // perform a non-destructive traversal of the rope, right node first, 780 // splatting each node's characters into a contiguous buffer. 781 782 size_t n = length(); 783 784 UniquePtr<CharT[], JS::FreePolicy> out; 785 if (maybecx) { 786 out.reset(maybecx->pod_arena_malloc<CharT>(destArenaId, n)); 787 } else { 788 out.reset(js_pod_arena_malloc<CharT>(destArenaId, n)); 789 } 790 791 if (!out) { 792 return nullptr; 793 } 794 795 Vector<const JSString*, 8, SystemAllocPolicy> nodeStack; 796 const JSString* str = this; 797 CharT* end = out.get() + str->length(); 798 while (true) { 799 if (str->isRope()) { 800 if (!nodeStack.append(str->asRope().leftChild())) { 801 if (maybecx) { 802 ReportOutOfMemory(maybecx); 803 } 804 return nullptr; 805 } 806 str = str->asRope().rightChild(); 807 } else { 808 end -= str->length(); 809 CopyChars(end, str->asLinear()); 810 if (nodeStack.empty()) { 811 break; 812 } 813 str = nodeStack.popCopy(); 814 } 815 } 816 MOZ_ASSERT(end == out.get()); 817 818 return out; 819 } 820 821 template <typename CharT> 822 void AddStringToHash(uint32_t* hash, const CharT* chars, size_t len) { 823 // It's tempting to use |HashString| instead of this loop, but that's 824 // slightly different than our existing implementation for non-ropes. We 825 // want to pretend we have a contiguous set of chars so we need to 826 // accumulate char by char rather than generate a new hash for substring 827 // and then accumulate that. 828 for (size_t i = 0; i < len; i++) { 829 *hash = mozilla::AddToHash(*hash, chars[i]); 830 } 831 } 832 833 void AddStringToHash(uint32_t* hash, const JSString* str) { 834 AutoCheckCannotGC nogc; 835 const auto& s = str->asLinear(); 836 if (s.hasLatin1Chars()) { 837 AddStringToHash(hash, s.latin1Chars(nogc), s.length()); 838 } else { 839 AddStringToHash(hash, s.twoByteChars(nogc), s.length()); 840 } 841 } 842 843 bool JSRope::hash(uint32_t* outHash) const { 844 Vector<const JSString*, 8, SystemAllocPolicy> nodeStack; 845 const JSString* str = this; 846 847 *outHash = 0; 848 849 while (true) { 850 if (str->isRope()) { 851 if (!nodeStack.append(str->asRope().rightChild())) { 852 return false; 853 } 854 str = str->asRope().leftChild(); 855 } else { 856 AddStringToHash(outHash, str); 857 if (nodeStack.empty()) { 858 break; 859 } 860 str = nodeStack.popCopy(); 861 } 862 } 863 864 return true; 865 } 866 867 #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) 868 void JSRope::dumpOwnRepresentationFields(js::JSONPrinter& json) const { 869 json.beginObjectProperty("leftChild"); 870 leftChild()->dumpRepresentationFields(json); 871 json.endObject(); 872 873 json.beginObjectProperty("rightChild"); 874 rightChild()->dumpRepresentationFields(json); 875 json.endObject(); 876 } 877 #endif 878 879 namespace js { 880 881 template <> 882 void CopyChars(char16_t* dest, const JSLinearString& str) { 883 AutoCheckCannotGC nogc; 884 if (str.hasTwoByteChars()) { 885 PodCopy(dest, str.twoByteChars(nogc), str.length()); 886 } else { 887 CopyAndInflateChars(dest, str.latin1Chars(nogc), str.length()); 888 } 889 } 890 891 template <> 892 void CopyChars(Latin1Char* dest, const JSLinearString& str) { 893 AutoCheckCannotGC nogc; 894 if (str.hasLatin1Chars()) { 895 PodCopy(dest, str.latin1Chars(nogc), str.length()); 896 } else { 897 /* 898 * When we flatten a TwoByte rope, we turn child ropes (including Latin1 899 * ropes) into TwoByte dependent strings. If one of these strings is 900 * also part of another Latin1 rope tree, we can have a Latin1 rope with 901 * a TwoByte descendent and we end up here when we flatten it. Although 902 * the chars are stored as TwoByte, we know they must be in the Latin1 903 * range, so we can safely deflate here. 904 */ 905 size_t len = str.length(); 906 const char16_t* chars = str.twoByteChars(nogc); 907 auto src = Span(chars, len); 908 MOZ_ASSERT(IsUtf16Latin1(src)); 909 LossyConvertUtf16toLatin1(src, AsWritableChars(Span(dest, len))); 910 } 911 } 912 913 } /* namespace js */ 914 915 template <typename CharT> 916 static constexpr uint32_t StringFlagsForCharType(uint32_t baseFlags) { 917 if constexpr (std::is_same_v<CharT, char16_t>) { 918 return baseFlags; 919 } 920 921 return baseFlags | JSString::LATIN1_CHARS_BIT; 922 } 923 924 static bool UpdateNurseryBuffersOnTransfer(js::Nursery& nursery, 925 JSExtensibleString* from, 926 JSString* to, void* chars, 927 size_t size) { 928 // Update the list of buffers associated with nursery cells when |buffer| is 929 // moved from string |from| to string |to|, depending on whether those strings 930 // are in the nursery or not. 931 932 if (from->hasStringBuffer()) { 933 // Note: addExtensibleStringBuffer is fallible so we have to call it before 934 // removeExtensibleStringBuffer. 935 // If both strings are in the nursery, avoid updating mallocedBufferBytes to 936 // not trigger an unnecessary minor GC in addMallocedBufferBytes. 937 bool updateMallocBytes = from->isTenured() || to->isTenured(); 938 if (!to->isTenured()) { 939 auto* linear = static_cast<JSLinearString*>(to); 940 if (!nursery.addExtensibleStringBuffer(linear, from->stringBuffer(), 941 updateMallocBytes)) { 942 return false; 943 } 944 } 945 if (!from->isTenured()) { 946 nursery.removeExtensibleStringBuffer(from, updateMallocBytes); 947 } 948 return true; 949 } 950 951 if (from->isTenured() && !to->isTenured()) { 952 // Tenured leftmost child is giving its chars buffer to the 953 // nursery-allocated root node. 954 if (!nursery.registerMallocedBuffer(chars, size)) { 955 return false; 956 } 957 } else if (!from->isTenured() && to->isTenured()) { 958 // Leftmost child is giving its nursery-held chars buffer to a 959 // tenured string. 960 nursery.removeMallocedBuffer(chars, size); 961 } 962 963 return true; 964 } 965 966 static bool CanReuseLeftmostBuffer(JSString* leftmostChild, size_t wholeLength, 967 bool hasTwoByteChars, bool isTenured) { 968 if (!leftmostChild->isExtensible()) { 969 return false; 970 } 971 972 JSExtensibleString& str = leftmostChild->asExtensible(); 973 974 // Don't mutate the StringBuffer if there are other references to it, possibly 975 // on other threads. 976 if (str.hasStringBuffer() && str.stringBuffer()->IsReadonly()) { 977 return false; 978 } 979 980 if (str.capacity() < wholeLength || 981 str.hasTwoByteChars() != hasTwoByteChars) { 982 return false; 983 } 984 985 // Don't try to reuse the buffer if the extensible string's characters are in 986 // the nursery and the (root) rope is tenured. 987 if (isTenured && !str.hasStringBuffer() && !str.ownsMallocedChars()) { 988 MOZ_ASSERT(IsInsideNursery(&str)); 989 return false; 990 } 991 992 return true; 993 } 994 995 JSLinearString* JSRope::flatten(JSContext* maybecx) { 996 mozilla::Maybe<AutoGeckoProfilerEntry> entry; 997 if (maybecx) { 998 entry.emplace(maybecx, "JSRope::flatten"); 999 } 1000 1001 JSLinearString* str = flattenInternal(); 1002 if (!str && maybecx) { 1003 ReportOutOfMemory(maybecx); 1004 } 1005 1006 return str; 1007 } 1008 1009 JSLinearString* JSRope::flattenInternal() { 1010 if (zone()->needsIncrementalBarrier()) { 1011 return flattenInternal<WithIncrementalBarrier>(); 1012 } 1013 1014 return flattenInternal<NoBarrier>(); 1015 } 1016 1017 template <JSRope::UsingBarrier usingBarrier> 1018 JSLinearString* JSRope::flattenInternal() { 1019 if (hasTwoByteChars()) { 1020 return flattenInternal<usingBarrier, char16_t>(this); 1021 } 1022 1023 return flattenInternal<usingBarrier, Latin1Char>(this); 1024 } 1025 1026 template <JSRope::UsingBarrier usingBarrier, typename CharT> 1027 /* static */ 1028 JSLinearString* JSRope::flattenInternal(JSRope* root) { 1029 /* 1030 * Consider the DAG of JSRopes rooted at |root|, with non-JSRopes as 1031 * its leaves. Mutate the root JSRope into a JSExtensibleString containing 1032 * the full flattened text that the root represents, and mutate all other 1033 * JSRopes in the interior of the DAG into JSDependentStrings that refer to 1034 * this new JSExtensibleString. 1035 * 1036 * If the leftmost leaf of our DAG is a JSExtensibleString, consider 1037 * stealing its buffer for use in our new root, and transforming it into a 1038 * JSDependentString too. Do not mutate any of the other leaves. 1039 * 1040 * Perform a depth-first dag traversal, splatting each node's characters 1041 * into a contiguous buffer. Visit each rope node three times: 1042 * 1. record position in the buffer and recurse into left child; 1043 * 2. recurse into the right child; 1044 * 3. transform the node into a dependent string. 1045 * To avoid maintaining a stack, tree nodes are mutated to indicate how many 1046 * times they have been visited. Since ropes can be dags, a node may be 1047 * encountered multiple times during traversal. However, step 3 above leaves 1048 * a valid dependent string, so everything works out. 1049 * 1050 * While ropes avoid all sorts of quadratic cases with string concatenation, 1051 * they can't help when ropes are immediately flattened. One idiomatic case 1052 * that we'd like to keep linear (and has traditionally been linear in SM 1053 * and other JS engines) is: 1054 * 1055 * while (...) { 1056 * s += ... 1057 * s.flatten 1058 * } 1059 * 1060 * Two behaviors accomplish this: 1061 * 1062 * - When the leftmost non-rope in the DAG we're flattening is a 1063 * JSExtensibleString with sufficient capacity to hold the entire 1064 * flattened string, we just flatten the DAG into its buffer. Then, when 1065 * we transform the root of the DAG from a JSRope into a 1066 * JSExtensibleString, we steal that buffer, and change the victim from a 1067 * JSExtensibleString to a JSDependentString. In this case, the left-hand 1068 * side of the string never needs to be copied. 1069 * 1070 * - Otherwise, we round up the total flattened size and create a fresh 1071 * JSExtensibleString with that much capacity. If this in turn becomes the 1072 * leftmost leaf of a subsequent flatten, we will hopefully be able to 1073 * fill it, as in the case above. 1074 * 1075 * Note that, even though the code for creating JSDependentStrings avoids 1076 * creating dependents of dependents, we can create that situation here: the 1077 * JSExtensibleStrings we transform into JSDependentStrings might have 1078 * JSDependentStrings pointing to them already. Stealing the buffer doesn't 1079 * change its address, only its owning JSExtensibleString, so all chars() 1080 * pointers in the JSDependentStrings are still valid. 1081 * 1082 * This chain of dependent strings could be problematic if the base string 1083 * moves, either because it was initially allocated in the nursery or it 1084 * gets deduplicated, because you might have a dependent -> 1085 * tenured dependent -> nursery base string, and the store buffer would 1086 * only capture the latter edge. Prevent this case from happening by 1087 * marking the root as nondeduplicatable if the extensible string 1088 * optimization applied. 1089 */ 1090 const size_t wholeLength = root->length(); 1091 size_t wholeCapacity; 1092 CharT* wholeChars; 1093 uint32_t newRootFlags = 0; 1094 1095 AutoCheckCannotGC nogc; 1096 1097 Nursery& nursery = root->runtimeFromMainThread()->gc.nursery(); 1098 1099 /* Find the left most string, containing the first string. */ 1100 JSRope* leftmostRope = root; 1101 while (leftmostRope->leftChild()->isRope()) { 1102 leftmostRope = &leftmostRope->leftChild()->asRope(); 1103 } 1104 JSString* leftmostChild = leftmostRope->leftChild(); 1105 1106 bool reuseLeftmostBuffer = CanReuseLeftmostBuffer( 1107 leftmostChild, wholeLength, std::is_same_v<CharT, char16_t>, 1108 root->isTenured()); 1109 1110 bool hasStringBuffer = false; 1111 if (reuseLeftmostBuffer) { 1112 JSExtensibleString& left = leftmostChild->asExtensible(); 1113 wholeCapacity = left.capacity(); 1114 wholeChars = const_cast<CharT*>(left.nonInlineChars<CharT>(nogc)); 1115 hasStringBuffer = left.hasStringBuffer(); 1116 1117 // Nursery::registerMallocedBuffer is fallible, so attempt it first before 1118 // doing anything irreversible. 1119 if (!UpdateNurseryBuffersOnTransfer(nursery, &left, root, wholeChars, 1120 wholeCapacity * sizeof(CharT))) { 1121 return nullptr; 1122 } 1123 } else { 1124 // If we can't reuse the leftmost child's buffer, allocate a new one. 1125 if (!AllocCharsForFlatten(nursery, root, wholeLength, &wholeChars, 1126 &wholeCapacity, &hasStringBuffer)) { 1127 return nullptr; 1128 } 1129 } 1130 1131 JSRope* str = root; 1132 CharT* pos = wholeChars; 1133 1134 JSRope* parent = nullptr; 1135 uint32_t parentFlag = 0; 1136 1137 first_visit_node: { 1138 MOZ_ASSERT_IF(str != root, parent && parentFlag); 1139 MOZ_ASSERT(!str->asRope().isBeingFlattened()); 1140 1141 ropeBarrierDuringFlattening<usingBarrier>(str); 1142 1143 JSString& left = *str->d.s.u2.left; 1144 str->d.s.u2.parent = parent; 1145 str->setFlagBit(parentFlag); 1146 parent = nullptr; 1147 parentFlag = 0; 1148 1149 if (left.isRope()) { 1150 /* Return to this node when 'left' done, then goto visit_right_child. */ 1151 parent = str; 1152 parentFlag = FLATTEN_VISIT_RIGHT; 1153 str = &left.asRope(); 1154 goto first_visit_node; 1155 } 1156 if (!(reuseLeftmostBuffer && pos == wholeChars)) { 1157 CopyChars(pos, left.asLinear()); 1158 } 1159 pos += left.length(); 1160 } 1161 1162 visit_right_child: { 1163 JSString& right = *str->d.s.u3.right; 1164 if (right.isRope()) { 1165 /* Return to this node when 'right' done, then goto finish_node. */ 1166 parent = str; 1167 parentFlag = FLATTEN_FINISH_NODE; 1168 str = &right.asRope(); 1169 goto first_visit_node; 1170 } 1171 CopyChars(pos, right.asLinear()); 1172 pos += right.length(); 1173 } 1174 1175 finish_node: { 1176 if (str == root) { 1177 goto finish_root; 1178 } 1179 1180 MOZ_ASSERT(pos >= wholeChars); 1181 CharT* chars = pos - str->length(); 1182 JSRope* strParent = str->d.s.u2.parent; 1183 str->setNonInlineChars(chars, /* usesStringBuffer = */ false); 1184 1185 MOZ_ASSERT(str->asRope().isBeingFlattened()); 1186 mozilla::DebugOnly<bool> visitRight = str->flags() & FLATTEN_VISIT_RIGHT; 1187 bool finishNode = str->flags() & FLATTEN_FINISH_NODE; 1188 MOZ_ASSERT(visitRight != finishNode); 1189 1190 // This also clears the flags related to flattening. 1191 str->setLengthAndFlags(str->length(), 1192 StringFlagsForCharType<CharT>(INIT_DEPENDENT_FLAGS)); 1193 str->d.s.u3.base = 1194 reinterpret_cast<JSLinearString*>(root); /* will be true on exit */ 1195 newRootFlags |= DEPENDED_ON_BIT; 1196 1197 // Every interior (rope) node in the rope's tree will be visited during 1198 // the traversal and post-barriered here, so earlier additions of 1199 // dependent.base -> root pointers are handled by this barrier as well. 1200 // 1201 // The only time post-barriers need do anything is when the root is in 1202 // the nursery. Note that the root was a rope but will be an extensible 1203 // string when we return, so it will not point to any strings and need 1204 // not be barriered. 1205 if (str->isTenured() && !root->isTenured()) { 1206 root->storeBuffer()->putWholeCell(str); 1207 } 1208 1209 str = strParent; 1210 if (finishNode) { 1211 goto finish_node; 1212 } 1213 MOZ_ASSERT(visitRight); 1214 goto visit_right_child; 1215 } 1216 1217 finish_root: 1218 // We traversed all the way back up to the root so we're finished. 1219 MOZ_ASSERT(str == root); 1220 MOZ_ASSERT(pos == wholeChars + wholeLength); 1221 1222 uint32_t flags = StringFlagsForCharType<CharT>(EXTENSIBLE_FLAGS); 1223 if (hasStringBuffer) { 1224 flags |= HAS_STRING_BUFFER_BIT; 1225 wholeChars[wholeLength] = '\0'; 1226 } 1227 root->setLengthAndFlags(wholeLength, flags); 1228 root->setNonInlineChars(wholeChars, hasStringBuffer); 1229 root->d.s.u3.capacity = wholeCapacity; 1230 AddCellMemory(root, wholeCapacity * sizeof(CharT), MemoryUse::StringContents); 1231 1232 if (reuseLeftmostBuffer) { 1233 // Remove memory association for left node we're about to make into a 1234 // dependent string. 1235 JSString& left = *leftmostChild; 1236 RemoveCellMemory(&left, left.allocSize(), MemoryUse::StringContents); 1237 1238 // Inherit NON_DEDUP_BIT from the leftmost string. 1239 newRootFlags |= left.flags() & NON_DEDUP_BIT; 1240 1241 // Set root's DEPENDED_ON_BIT because the leftmost string is now a 1242 // dependent. 1243 newRootFlags |= DEPENDED_ON_BIT; 1244 1245 uint32_t flags = INIT_DEPENDENT_FLAGS; 1246 if (left.inStringToAtomCache()) { 1247 flags |= IN_STRING_TO_ATOM_CACHE; 1248 } 1249 // If left was depended on, we need to make sure we preserve that. Even 1250 // though the string that depended on left's buffer will now depend on 1251 // root's buffer, if left is the only edge to root, replacing left with an 1252 // atom ref would break that edge and allow root's buffer to be freed. 1253 if (left.isDependedOn()) { 1254 flags |= DEPENDED_ON_BIT; 1255 } 1256 left.setLengthAndFlags(left.length(), StringFlagsForCharType<CharT>(flags)); 1257 left.d.s.u3.base = &root->asLinear(); 1258 if (left.isTenured() && !root->isTenured()) { 1259 // leftmost child -> root is a tenured -> nursery edge. Put the leftmost 1260 // child in the store buffer and prevent the root's chars from moving or 1261 // being freed (because the leftmost child may have a tenured dependent 1262 // string that cannot be updated.) 1263 root->storeBuffer()->putWholeCell(&left); 1264 newRootFlags |= NON_DEDUP_BIT; 1265 } 1266 } 1267 1268 root->setHeaderFlagBit(newRootFlags); 1269 1270 return &root->asLinear(); 1271 } 1272 1273 template <JSRope::UsingBarrier usingBarrier> 1274 /* static */ 1275 inline void JSRope::ropeBarrierDuringFlattening(JSRope* rope) { 1276 MOZ_ASSERT(!rope->isBeingFlattened()); 1277 if constexpr (usingBarrier) { 1278 gc::PreWriteBarrierDuringFlattening(rope->leftChild()); 1279 gc::PreWriteBarrierDuringFlattening(rope->rightChild()); 1280 } 1281 } 1282 1283 template <AllowGC allowGC> 1284 static JSLinearString* EnsureLinear( 1285 JSContext* cx, 1286 typename MaybeRooted<JSString*, allowGC>::HandleType string) { 1287 JSLinearString* linear = string->ensureLinear(cx); 1288 // Don't report an exception if GC is not allowed, just return nullptr. 1289 if (!linear && !allowGC) { 1290 cx->recoverFromOutOfMemory(); 1291 } 1292 return linear; 1293 } 1294 1295 template <AllowGC allowGC> 1296 JSString* js::ConcatStrings( 1297 JSContext* cx, typename MaybeRooted<JSString*, allowGC>::HandleType left, 1298 typename MaybeRooted<JSString*, allowGC>::HandleType right, gc::Heap heap) { 1299 MOZ_ASSERT_IF(!left->isAtom(), cx->isInsideCurrentZone(left)); 1300 MOZ_ASSERT_IF(!right->isAtom(), cx->isInsideCurrentZone(right)); 1301 1302 size_t leftLen = left->length(); 1303 if (leftLen == 0) { 1304 return right; 1305 } 1306 1307 size_t rightLen = right->length(); 1308 if (rightLen == 0) { 1309 return left; 1310 } 1311 1312 size_t wholeLength = leftLen + rightLen; 1313 if (MOZ_UNLIKELY(wholeLength > JSString::MAX_LENGTH)) { 1314 // Don't report an exception if GC is not allowed, just return nullptr. 1315 if (allowGC) { 1316 js::ReportOversizedAllocation(cx, JSMSG_ALLOC_OVERFLOW); 1317 } 1318 return nullptr; 1319 } 1320 1321 bool isLatin1 = left->hasLatin1Chars() && right->hasLatin1Chars(); 1322 bool canUseInline = isLatin1 1323 ? JSInlineString::lengthFits<Latin1Char>(wholeLength) 1324 : JSInlineString::lengthFits<char16_t>(wholeLength); 1325 if (canUseInline) { 1326 Latin1Char* latin1Buf = nullptr; // initialize to silence GCC warning 1327 char16_t* twoByteBuf = nullptr; // initialize to silence GCC warning 1328 JSInlineString* str = 1329 isLatin1 1330 ? AllocateInlineString<allowGC>(cx, wholeLength, &latin1Buf, heap) 1331 : AllocateInlineString<allowGC>(cx, wholeLength, &twoByteBuf, heap); 1332 if (!str) { 1333 return nullptr; 1334 } 1335 1336 AutoCheckCannotGC nogc; 1337 JSLinearString* leftLinear = EnsureLinear<allowGC>(cx, left); 1338 if (!leftLinear) { 1339 return nullptr; 1340 } 1341 JSLinearString* rightLinear = EnsureLinear<allowGC>(cx, right); 1342 if (!rightLinear) { 1343 return nullptr; 1344 } 1345 1346 if (isLatin1) { 1347 PodCopy(latin1Buf, leftLinear->latin1Chars(nogc), leftLen); 1348 PodCopy(latin1Buf + leftLen, rightLinear->latin1Chars(nogc), rightLen); 1349 } else { 1350 if (leftLinear->hasTwoByteChars()) { 1351 PodCopy(twoByteBuf, leftLinear->twoByteChars(nogc), leftLen); 1352 } else { 1353 CopyAndInflateChars(twoByteBuf, leftLinear->latin1Chars(nogc), leftLen); 1354 } 1355 if (rightLinear->hasTwoByteChars()) { 1356 PodCopy(twoByteBuf + leftLen, rightLinear->twoByteChars(nogc), 1357 rightLen); 1358 } else { 1359 CopyAndInflateChars(twoByteBuf + leftLen, 1360 rightLinear->latin1Chars(nogc), rightLen); 1361 } 1362 } 1363 1364 return str; 1365 } 1366 1367 return JSRope::new_<allowGC>(cx, left, right, wholeLength, heap); 1368 } 1369 1370 template JSString* js::ConcatStrings<CanGC>(JSContext* cx, HandleString left, 1371 HandleString right, gc::Heap heap); 1372 1373 template JSString* js::ConcatStrings<NoGC>(JSContext* cx, JSString* const& left, 1374 JSString* const& right, 1375 gc::Heap heap); 1376 1377 bool JSLinearString::hasCharsInCollectedNurseryRegion() const { 1378 if (isPermanentAtom()) { 1379 // Nursery::inCollectedRegion(void*) should only be called on the nursery's 1380 // main thread to avoid races. Permanent atoms can be shared with worker 1381 // threads but atoms are never allocated in the nursery. 1382 MOZ_ASSERT(isTenured()); 1383 return false; 1384 } 1385 auto& nursery = runtimeFromMainThread()->gc.nursery(); 1386 if (isInline()) { 1387 return nursery.inCollectedRegion(this); 1388 } 1389 return nursery.inCollectedRegion(nonInlineCharsRaw()); 1390 } 1391 1392 #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) 1393 void JSDependentString::dumpOwnRepresentationFields( 1394 js::JSONPrinter& json) const { 1395 json.property("baseOffset", baseOffset()); 1396 json.beginObjectProperty("base"); 1397 base()->dumpRepresentationFields(json); 1398 json.endObject(); 1399 } 1400 #endif 1401 1402 bool js::EqualChars(const JSLinearString* str1, const JSLinearString* str2) { 1403 // Assert this isn't called for strings the caller should handle with a fast 1404 // path. 1405 MOZ_ASSERT(str1->length() == str2->length()); 1406 MOZ_ASSERT(str1 != str2); 1407 MOZ_ASSERT(!str1->isAtom() || !str2->isAtom()); 1408 1409 size_t len = str1->length(); 1410 1411 AutoCheckCannotGC nogc; 1412 if (str1->hasTwoByteChars()) { 1413 if (str2->hasTwoByteChars()) { 1414 return EqualChars(str1->twoByteChars(nogc), str2->twoByteChars(nogc), 1415 len); 1416 } 1417 1418 return EqualChars(str2->latin1Chars(nogc), str1->twoByteChars(nogc), len); 1419 } 1420 1421 if (str2->hasLatin1Chars()) { 1422 return EqualChars(str1->latin1Chars(nogc), str2->latin1Chars(nogc), len); 1423 } 1424 1425 return EqualChars(str1->latin1Chars(nogc), str2->twoByteChars(nogc), len); 1426 } 1427 1428 bool js::HasSubstringAt(const JSLinearString* text, const JSLinearString* pat, 1429 size_t start) { 1430 MOZ_ASSERT(start + pat->length() <= text->length()); 1431 1432 size_t patLen = pat->length(); 1433 1434 AutoCheckCannotGC nogc; 1435 if (text->hasLatin1Chars()) { 1436 const Latin1Char* textChars = text->latin1Chars(nogc) + start; 1437 if (pat->hasLatin1Chars()) { 1438 return EqualChars(textChars, pat->latin1Chars(nogc), patLen); 1439 } 1440 1441 return EqualChars(textChars, pat->twoByteChars(nogc), patLen); 1442 } 1443 1444 const char16_t* textChars = text->twoByteChars(nogc) + start; 1445 if (pat->hasTwoByteChars()) { 1446 return EqualChars(textChars, pat->twoByteChars(nogc), patLen); 1447 } 1448 1449 return EqualChars(pat->latin1Chars(nogc), textChars, patLen); 1450 } 1451 1452 bool js::EqualStrings(JSContext* cx, JSString* str1, JSString* str2, 1453 bool* result) { 1454 if (str1 == str2) { 1455 *result = true; 1456 return true; 1457 } 1458 if (str1->length() != str2->length()) { 1459 *result = false; 1460 return true; 1461 } 1462 if (str1->isAtom() && str2->isAtom()) { 1463 *result = false; 1464 return true; 1465 } 1466 1467 JSLinearString* linear1 = str1->ensureLinear(cx); 1468 if (!linear1) { 1469 return false; 1470 } 1471 JSLinearString* linear2 = str2->ensureLinear(cx); 1472 if (!linear2) { 1473 return false; 1474 } 1475 1476 *result = EqualChars(linear1, linear2); 1477 return true; 1478 } 1479 1480 bool js::EqualStrings(const JSLinearString* str1, const JSLinearString* str2) { 1481 if (str1 == str2) { 1482 return true; 1483 } 1484 if (str1->length() != str2->length()) { 1485 return false; 1486 } 1487 if (str1->isAtom() && str2->isAtom()) { 1488 return false; 1489 } 1490 return EqualChars(str1, str2); 1491 } 1492 1493 int32_t js::CompareChars(const char16_t* s1, size_t len1, 1494 const JSLinearString* s2) { 1495 AutoCheckCannotGC nogc; 1496 return s2->hasLatin1Chars() 1497 ? CompareChars(s1, len1, s2->latin1Chars(nogc), s2->length()) 1498 : CompareChars(s1, len1, s2->twoByteChars(nogc), s2->length()); 1499 } 1500 1501 static int32_t CompareStringsImpl(const JSLinearString* str1, 1502 const JSLinearString* str2) { 1503 size_t len1 = str1->length(); 1504 size_t len2 = str2->length(); 1505 1506 AutoCheckCannotGC nogc; 1507 if (str1->hasLatin1Chars()) { 1508 const Latin1Char* chars1 = str1->latin1Chars(nogc); 1509 return str2->hasLatin1Chars() 1510 ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2) 1511 : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2); 1512 } 1513 1514 const char16_t* chars1 = str1->twoByteChars(nogc); 1515 return str2->hasLatin1Chars() 1516 ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2) 1517 : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2); 1518 } 1519 1520 bool js::CompareStrings(JSContext* cx, JSString* str1, JSString* str2, 1521 int32_t* result) { 1522 MOZ_ASSERT(str1); 1523 MOZ_ASSERT(str2); 1524 1525 if (str1 == str2) { 1526 *result = 0; 1527 return true; 1528 } 1529 1530 JSLinearString* linear1 = str1->ensureLinear(cx); 1531 if (!linear1) { 1532 return false; 1533 } 1534 1535 JSLinearString* linear2 = str2->ensureLinear(cx); 1536 if (!linear2) { 1537 return false; 1538 } 1539 1540 *result = CompareStringsImpl(linear1, linear2); 1541 return true; 1542 } 1543 1544 int32_t js::CompareStrings(const JSLinearString* str1, 1545 const JSLinearString* str2) { 1546 MOZ_ASSERT(str1); 1547 MOZ_ASSERT(str2); 1548 1549 if (str1 == str2) { 1550 return 0; 1551 } 1552 return CompareStringsImpl(str1, str2); 1553 } 1554 1555 int32_t js::CompareStrings(const JSOffThreadAtom* str1, 1556 const JSOffThreadAtom* str2) { 1557 MOZ_ASSERT(str1); 1558 MOZ_ASSERT(str2); 1559 1560 if (str1 == str2) { 1561 return 0; 1562 } 1563 1564 size_t len1 = str1->length(); 1565 size_t len2 = str2->length(); 1566 1567 AutoCheckCannotGC nogc; 1568 if (str1->hasLatin1Chars()) { 1569 const Latin1Char* chars1 = str1->latin1Chars(nogc); 1570 return str2->hasLatin1Chars() 1571 ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2) 1572 : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2); 1573 } 1574 1575 const char16_t* chars1 = str1->twoByteChars(nogc); 1576 return str2->hasLatin1Chars() 1577 ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2) 1578 : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2); 1579 } 1580 1581 bool js::StringIsAscii(const JSLinearString* str) { 1582 JS::AutoCheckCannotGC nogc; 1583 if (str->hasLatin1Chars()) { 1584 return mozilla::IsAscii( 1585 AsChars(Span(str->latin1Chars(nogc), str->length()))); 1586 } 1587 return mozilla::IsAscii(Span(str->twoByteChars(nogc), str->length())); 1588 } 1589 1590 bool js::StringEqualsAscii(const JSLinearString* str, const char* asciiBytes) { 1591 return StringEqualsAscii(str, asciiBytes, strlen(asciiBytes)); 1592 } 1593 1594 bool js::StringEqualsAscii(const JSLinearString* str, const char* asciiBytes, 1595 size_t length) { 1596 MOZ_ASSERT(JS::StringIsASCII(Span(asciiBytes, length))); 1597 1598 if (length != str->length()) { 1599 return false; 1600 } 1601 1602 const Latin1Char* latin1 = reinterpret_cast<const Latin1Char*>(asciiBytes); 1603 1604 AutoCheckCannotGC nogc; 1605 return str->hasLatin1Chars() 1606 ? EqualChars(latin1, str->latin1Chars(nogc), length) 1607 : EqualChars(latin1, str->twoByteChars(nogc), length); 1608 } 1609 1610 template <typename CharT> 1611 bool js::CheckStringIsIndex(const CharT* s, size_t length, uint32_t* indexp) { 1612 MOZ_ASSERT(length > 0); 1613 MOZ_ASSERT(length <= UINT32_CHAR_BUFFER_LENGTH); 1614 MOZ_ASSERT(IsAsciiDigit(*s), 1615 "caller's fast path must have checked first char"); 1616 1617 RangedPtr<const CharT> cp(s, length); 1618 const RangedPtr<const CharT> end(s + length, s, length); 1619 1620 uint32_t index = AsciiDigitToNumber(*cp++); 1621 uint32_t oldIndex = 0; 1622 uint32_t c = 0; 1623 1624 if (index != 0) { 1625 // Consume remaining characters only if the first character isn't '0'. 1626 while (cp < end && IsAsciiDigit(*cp)) { 1627 oldIndex = index; 1628 c = AsciiDigitToNumber(*cp); 1629 index = 10 * index + c; 1630 cp++; 1631 } 1632 } 1633 1634 // It's not an integer index if there are characters after the number. 1635 if (cp != end) { 1636 return false; 1637 } 1638 1639 // Look out for "4294967295" and larger-number strings that fit in 1640 // UINT32_CHAR_BUFFER_LENGTH: only unsigned 32-bit integers less than or equal 1641 // to MAX_ARRAY_INDEX shall pass. 1642 if (oldIndex < MAX_ARRAY_INDEX / 10 || 1643 (oldIndex == MAX_ARRAY_INDEX / 10 && c <= (MAX_ARRAY_INDEX % 10))) { 1644 MOZ_ASSERT(index <= MAX_ARRAY_INDEX); 1645 *indexp = index; 1646 return true; 1647 } 1648 1649 return false; 1650 } 1651 1652 template bool js::CheckStringIsIndex(const Latin1Char* s, size_t length, 1653 uint32_t* indexp); 1654 template bool js::CheckStringIsIndex(const char16_t* s, size_t length, 1655 uint32_t* indexp); 1656 1657 template <typename CharT> 1658 static uint32_t AtomCharsToIndex(const CharT* s, size_t length) { 1659 // Chars are known to be a valid index value (as determined by 1660 // CheckStringIsIndex) that didn't fit in the "index value" bits in the 1661 // header. 1662 1663 MOZ_ASSERT(length > 0); 1664 MOZ_ASSERT(length <= UINT32_CHAR_BUFFER_LENGTH); 1665 1666 RangedPtr<const CharT> cp(s, length); 1667 const RangedPtr<const CharT> end(s + length, s, length); 1668 1669 MOZ_ASSERT(IsAsciiDigit(*cp)); 1670 uint32_t index = AsciiDigitToNumber(*cp++); 1671 MOZ_ASSERT(index != 0); 1672 1673 while (cp < end) { 1674 MOZ_ASSERT(IsAsciiDigit(*cp)); 1675 index = 10 * index + AsciiDigitToNumber(*cp); 1676 cp++; 1677 } 1678 1679 MOZ_ASSERT(index <= MAX_ARRAY_INDEX); 1680 return index; 1681 } 1682 1683 uint32_t JSAtom::getIndexSlow() const { 1684 MOZ_ASSERT(isIndex()); 1685 MOZ_ASSERT(!hasIndexValue()); 1686 1687 size_t len = length(); 1688 1689 AutoCheckCannotGC nogc; 1690 return hasLatin1Chars() ? AtomCharsToIndex(latin1Chars(nogc), len) 1691 : AtomCharsToIndex(twoByteChars(nogc), len); 1692 } 1693 1694 uint32_t JSOffThreadAtom::getIndexSlow() const { 1695 MOZ_ASSERT(isIndex()); 1696 MOZ_ASSERT(!hasIndexValue()); 1697 1698 size_t len = length(); 1699 1700 AutoCheckCannotGC nogc; 1701 return hasLatin1Chars() ? AtomCharsToIndex(latin1Chars(nogc), len) 1702 : AtomCharsToIndex(twoByteChars(nogc), len); 1703 } 1704 1705 // Ensure that the incoming s.chars pointer is stable, as in, it cannot be 1706 // changed even across a GC. That requires that the string that owns the chars 1707 // not be collected or deduplicated. 1708 void AutoStableStringChars::holdStableChars(JSLinearString* s) { 1709 while (s->hasBase()) { 1710 s = s->base(); 1711 } 1712 if (!s->isTenured()) { 1713 s->setNonDeduplicatable(); 1714 } 1715 s_ = s; 1716 } 1717 1718 bool AutoStableStringChars::init(JSContext* cx, JSString* s) { 1719 JSLinearString* linearString = s->ensureLinear(cx); 1720 if (!linearString) { 1721 return false; 1722 } 1723 1724 linearString->setDependedOn(); 1725 1726 MOZ_ASSERT(state_ == Uninitialized); 1727 length_ = linearString->length(); 1728 1729 // Inline and nursery-allocated chars may move during a GC, so copy them 1730 // out into a temporary malloced buffer. Note that we cannot update the 1731 // string itself with a malloced buffer, because there may be dependent 1732 // strings that are using the original chars. 1733 if (linearString->hasMovableChars()) { 1734 return linearString->hasTwoByteChars() ? copyTwoByteChars(cx, linearString) 1735 : copyLatin1Chars(cx, linearString); 1736 } 1737 1738 if (linearString->hasLatin1Chars()) { 1739 state_ = Latin1; 1740 latin1Chars_ = linearString->rawLatin1Chars(); 1741 } else { 1742 state_ = TwoByte; 1743 twoByteChars_ = linearString->rawTwoByteChars(); 1744 } 1745 1746 holdStableChars(linearString); 1747 return true; 1748 } 1749 1750 bool AutoStableStringChars::initTwoByte(JSContext* cx, JSString* s) { 1751 JSLinearString* linearString = s->ensureLinear(cx); 1752 if (!linearString) { 1753 return false; 1754 } 1755 1756 linearString->setDependedOn(); 1757 1758 MOZ_ASSERT(state_ == Uninitialized); 1759 length_ = linearString->length(); 1760 1761 if (linearString->hasLatin1Chars()) { 1762 return copyAndInflateLatin1Chars(cx, linearString); 1763 } 1764 1765 // Copy movable chars since they may be moved by GC (see above). 1766 if (linearString->hasMovableChars()) { 1767 return copyTwoByteChars(cx, linearString); 1768 } 1769 1770 state_ = TwoByte; 1771 twoByteChars_ = linearString->rawTwoByteChars(); 1772 1773 holdStableChars(linearString); 1774 return true; 1775 } 1776 1777 template <typename T> 1778 T* AutoStableStringChars::allocOwnChars(JSContext* cx, size_t count) { 1779 static_assert( 1780 InlineCapacity >= 1781 sizeof(JS::Latin1Char) * JSFatInlineString::MAX_LENGTH_LATIN1 && 1782 InlineCapacity >= 1783 sizeof(char16_t) * JSFatInlineString::MAX_LENGTH_TWO_BYTE, 1784 "InlineCapacity too small to hold fat inline strings"); 1785 1786 static_assert(JSString::MAX_LENGTH * sizeof(T) >= JSString::MAX_LENGTH, 1787 "Size calculation can overflow"); 1788 MOZ_ASSERT(count <= JSString::MAX_LENGTH); 1789 size_t size = sizeof(T) * count; 1790 1791 ownChars_.emplace(cx); 1792 if (!ownChars_->resize(size)) { 1793 ownChars_.reset(); 1794 return nullptr; 1795 } 1796 1797 return reinterpret_cast<T*>(ownChars_->begin()); 1798 } 1799 1800 bool AutoStableStringChars::copyAndInflateLatin1Chars( 1801 JSContext* cx, JSLinearString* linearString) { 1802 MOZ_ASSERT(state_ == Uninitialized); 1803 MOZ_ASSERT(s_ == nullptr); 1804 1805 char16_t* chars = allocOwnChars<char16_t>(cx, length_); 1806 if (!chars) { 1807 return false; 1808 } 1809 1810 // Copy |src[0..length]| to |dest[0..length]| when copying doesn't narrow and 1811 // therefore can't lose information. 1812 auto src = AsChars(Span(linearString->rawLatin1Chars(), length_)); 1813 auto dest = Span(chars, length_); 1814 ConvertLatin1toUtf16(src, dest); 1815 1816 state_ = TwoByte; 1817 twoByteChars_ = chars; 1818 s_ = linearString; 1819 return true; 1820 } 1821 1822 bool AutoStableStringChars::copyLatin1Chars(JSContext* cx, 1823 JSLinearString* linearString) { 1824 MOZ_ASSERT(state_ == Uninitialized); 1825 MOZ_ASSERT(s_ == nullptr); 1826 1827 JS::Latin1Char* chars = allocOwnChars<JS::Latin1Char>(cx, length_); 1828 if (!chars) { 1829 return false; 1830 } 1831 1832 PodCopy(chars, linearString->rawLatin1Chars(), length_); 1833 1834 state_ = Latin1; 1835 latin1Chars_ = chars; 1836 s_ = linearString; 1837 return true; 1838 } 1839 1840 bool AutoStableStringChars::copyTwoByteChars(JSContext* cx, 1841 JSLinearString* linearString) { 1842 MOZ_ASSERT(state_ == Uninitialized); 1843 MOZ_ASSERT(s_ == nullptr); 1844 1845 char16_t* chars = allocOwnChars<char16_t>(cx, length_); 1846 if (!chars) { 1847 return false; 1848 } 1849 1850 PodCopy(chars, linearString->rawTwoByteChars(), length_); 1851 1852 state_ = TwoByte; 1853 twoByteChars_ = chars; 1854 s_ = linearString; 1855 return true; 1856 } 1857 1858 template <> 1859 bool JS::SourceText<char16_t>::initMaybeBorrowed( 1860 JSContext* cx, JS::AutoStableStringChars& linearChars) { 1861 MOZ_ASSERT(linearChars.isTwoByte(), 1862 "AutoStableStringChars must be initialized with char16_t"); 1863 1864 const char16_t* chars = linearChars.twoByteChars(); 1865 size_t length = linearChars.length(); 1866 JS::SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller() 1867 ? JS::SourceOwnership::TakeOwnership 1868 : JS::SourceOwnership::Borrowed; 1869 return initImpl(cx, chars, length, ownership); 1870 } 1871 1872 template <> 1873 bool JS::SourceText<char16_t>::initMaybeBorrowed( 1874 JS::FrontendContext* fc, JS::AutoStableStringChars& linearChars) { 1875 MOZ_ASSERT(linearChars.isTwoByte(), 1876 "AutoStableStringChars must be initialized with char16_t"); 1877 1878 const char16_t* chars = linearChars.twoByteChars(); 1879 size_t length = linearChars.length(); 1880 JS::SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller() 1881 ? JS::SourceOwnership::TakeOwnership 1882 : JS::SourceOwnership::Borrowed; 1883 return initImpl(fc, chars, length, ownership); 1884 } 1885 1886 #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) 1887 void JSAtom::dump(js::GenericPrinter& out) { 1888 out.printf("JSAtom* (%p) = ", (void*)this); 1889 this->JSString::dump(out); 1890 } 1891 1892 void JSAtom::dump() { 1893 Fprinter out(stderr); 1894 dump(out); 1895 } 1896 1897 void JSExternalString::dumpOwnRepresentationFields( 1898 js::JSONPrinter& json) const { 1899 json.formatProperty("callbacks", "(JSExternalStringCallbacks*)0x%p", 1900 callbacks()); 1901 } 1902 #endif /* defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) */ 1903 1904 template <JS::ContractBaseChain contract> 1905 static JSLinearString* NewDependentStringHelper(JSContext* cx, 1906 JSString* baseArg, size_t start, 1907 size_t length, gc::Heap heap) { 1908 if (length == 0) { 1909 return cx->emptyString(); 1910 } 1911 1912 JSLinearString* base = baseArg->ensureLinear(cx); 1913 if (!base) { 1914 return nullptr; 1915 } 1916 1917 if (start == 0 && length == base->length()) { 1918 return base; 1919 } 1920 1921 bool useInline; 1922 if (base->hasTwoByteChars()) { 1923 AutoCheckCannotGC nogc; 1924 const char16_t* chars = base->twoByteChars(nogc) + start; 1925 if (JSLinearString* staticStr = cx->staticStrings().lookup(chars, length)) { 1926 return staticStr; 1927 } 1928 useInline = JSInlineString::lengthFits<char16_t>(length); 1929 } else { 1930 AutoCheckCannotGC nogc; 1931 const Latin1Char* chars = base->latin1Chars(nogc) + start; 1932 if (JSLinearString* staticStr = cx->staticStrings().lookup(chars, length)) { 1933 return staticStr; 1934 } 1935 useInline = JSInlineString::lengthFits<Latin1Char>(length); 1936 } 1937 1938 if (useInline) { 1939 Rooted<JSLinearString*> rootedBase(cx, base); 1940 1941 // Do not create a dependent string that would fit into an inline string. 1942 // First, that could create a string dependent on an inline base string's 1943 // chars, which would be an awkward moving-GC hazard. Second, this makes 1944 // it more likely to have a very short string keep a very long string alive. 1945 if (base->hasTwoByteChars()) { 1946 return NewInlineString<char16_t>(cx, rootedBase, start, length, heap); 1947 } 1948 return NewInlineString<Latin1Char>(cx, rootedBase, start, length, heap); 1949 } 1950 1951 return JSDependentString::newImpl_<contract>(cx, base, start, length, heap); 1952 } 1953 1954 JSLinearString* js::NewDependentString(JSContext* cx, JSString* baseArg, 1955 size_t start, size_t length, 1956 gc::Heap heap) { 1957 return NewDependentStringHelper<JS::ContractBaseChain::Contract>( 1958 cx, baseArg, start, length, heap); 1959 } 1960 1961 JSLinearString* js::NewDependentStringForTesting(JSContext* cx, 1962 JSString* baseArg, 1963 size_t start, size_t length, 1964 JS::ContractBaseChain contract, 1965 gc::Heap heap) { 1966 if (contract == JS::ContractBaseChain::Contract) { 1967 return NewDependentStringHelper<JS::ContractBaseChain::Contract>( 1968 cx, baseArg, start, length, heap); 1969 } 1970 return NewDependentStringHelper<JS::ContractBaseChain::AllowLong>( 1971 cx, baseArg, start, length, heap); 1972 } 1973 1974 static constexpr bool CanStoreCharsAsLatin1(const JS::Latin1Char* s, 1975 size_t length) { 1976 return true; 1977 } 1978 1979 static inline bool CanStoreCharsAsLatin1(const char16_t* s, size_t length) { 1980 return IsUtf16Latin1(Span(s, length)); 1981 } 1982 1983 /** 1984 * Copy |src[0..length]| to |dest[0..length]| when copying *does* narrow, but 1985 * the user guarantees every runtime |src[i]| value can be stored without change 1986 * of value in |dest[i]|. 1987 */ 1988 static inline void FillFromCompatible(unsigned char* dest, const char16_t* src, 1989 size_t length) { 1990 LossyConvertUtf16toLatin1(Span(src, length), 1991 AsWritableChars(Span(dest, length))); 1992 } 1993 1994 template <AllowGC allowGC> 1995 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringDeflated( 1996 JSContext* cx, const mozilla::Range<const char16_t>& chars, 1997 gc::Heap heap = gc::Heap::Default) { 1998 size_t len = chars.length(); 1999 Latin1Char* storage; 2000 JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage, heap); 2001 if (!str) { 2002 return nullptr; 2003 } 2004 2005 MOZ_ASSERT(CanStoreCharsAsLatin1(chars.begin().get(), len)); 2006 FillFromCompatible(storage, chars.begin().get(), len); 2007 return str; 2008 } 2009 2010 template <AllowGC allowGC> 2011 static JSLinearString* NewStringDeflated(JSContext* cx, const char16_t* s, 2012 size_t n, gc::Heap heap) { 2013 if (JSLinearString* str = TryEmptyOrStaticString(cx, s, n)) { 2014 return str; 2015 } 2016 2017 if (JSInlineString::lengthFits<Latin1Char>(n)) { 2018 return NewInlineStringDeflated<allowGC>( 2019 cx, mozilla::Range<const char16_t>(s, n), heap); 2020 } 2021 2022 JS::Rooted<JSString::OwnedChars<Latin1Char>> news( 2023 cx, AllocChars<Latin1Char>(cx, n, heap)); 2024 if (!news) { 2025 if (!allowGC) { 2026 cx->recoverFromOutOfMemory(); 2027 } 2028 return nullptr; 2029 } 2030 2031 MOZ_ASSERT(CanStoreCharsAsLatin1(s, n)); 2032 FillFromCompatible(news.data(), s, n); 2033 2034 return JSLinearString::new_<allowGC, Latin1Char>(cx, &news, heap); 2035 } 2036 2037 static MOZ_ALWAYS_INLINE JSAtom* NewInlineAtomDeflated(JSContext* cx, 2038 const char16_t* chars, 2039 size_t length, 2040 js::HashNumber hash) { 2041 Latin1Char* storage; 2042 JSAtom* str = AllocateInlineAtom(cx, length, &storage, hash); 2043 if (!str) { 2044 return nullptr; 2045 } 2046 2047 MOZ_ASSERT(CanStoreCharsAsLatin1(chars, length)); 2048 FillFromCompatible(storage, chars, length); 2049 return str; 2050 } 2051 2052 static JSAtom* NewAtomDeflatedValidLength(JSContext* cx, const char16_t* s, 2053 size_t n, js::HashNumber hash) { 2054 if (JSAtom::lengthFitsInline<Latin1Char>(n)) { 2055 return NewInlineAtomDeflated(cx, s, n, hash); 2056 } 2057 2058 JSString::OwnedChars<Latin1Char> newChars( 2059 AllocAtomCharsValidLength<Latin1Char>(cx, n)); 2060 if (!newChars) { 2061 return nullptr; 2062 } 2063 2064 MOZ_ASSERT(CanStoreCharsAsLatin1(s, n)); 2065 FillFromCompatible(newChars.data(), s, n); 2066 2067 return JSAtom::newValidLength<Latin1Char>(cx, newChars, hash); 2068 } 2069 2070 template <AllowGC allowGC, typename CharT> 2071 JSLinearString* js::NewStringDontDeflate( 2072 JSContext* cx, UniquePtr<CharT[], JS::FreePolicy> chars, size_t length, 2073 gc::Heap heap) { 2074 if (JSLinearString* str = TryEmptyOrStaticString(cx, chars.get(), length)) { 2075 return str; 2076 } 2077 2078 if (JSInlineString::lengthFits<CharT>(length)) { 2079 // |chars.get()| is safe because 1) |NewInlineString| necessarily *copies*, 2080 // and 2) |chars| frees its contents only when this function returns. 2081 return NewInlineString<allowGC>( 2082 cx, mozilla::Range<const CharT>(chars.get(), length), heap); 2083 } 2084 2085 JS::Rooted<JSString::OwnedChars<CharT>> ownedChars(cx, std::move(chars), 2086 length); 2087 return JSLinearString::new_<allowGC, CharT>(cx, &ownedChars, heap); 2088 } 2089 2090 template JSLinearString* js::NewStringDontDeflate<CanGC>( 2091 JSContext* cx, UniqueTwoByteChars chars, size_t length, gc::Heap heap); 2092 2093 template JSLinearString* js::NewStringDontDeflate<NoGC>( 2094 JSContext* cx, UniqueTwoByteChars chars, size_t length, gc::Heap heap); 2095 2096 template JSLinearString* js::NewStringDontDeflate<CanGC>( 2097 JSContext* cx, UniqueLatin1Chars chars, size_t length, gc::Heap heap); 2098 2099 template JSLinearString* js::NewStringDontDeflate<NoGC>(JSContext* cx, 2100 UniqueLatin1Chars chars, 2101 size_t length, 2102 gc::Heap heap); 2103 2104 template <AllowGC allowGC, typename CharT> 2105 JSLinearString* js::NewString(JSContext* cx, 2106 UniquePtr<CharT[], JS::FreePolicy> chars, 2107 size_t length, gc::Heap heap) { 2108 if constexpr (std::is_same_v<CharT, char16_t>) { 2109 if (CanStoreCharsAsLatin1(chars.get(), length)) { 2110 // Deflating copies from |chars.get()| and lets |chars| be freed on 2111 // return. 2112 return NewStringDeflated<allowGC>(cx, chars.get(), length, heap); 2113 } 2114 } 2115 2116 return NewStringDontDeflate<allowGC>(cx, std::move(chars), length, heap); 2117 } 2118 2119 template JSLinearString* js::NewString<CanGC>(JSContext* cx, 2120 UniqueTwoByteChars chars, 2121 size_t length, gc::Heap heap); 2122 2123 template JSLinearString* js::NewString<NoGC>(JSContext* cx, 2124 UniqueTwoByteChars chars, 2125 size_t length, gc::Heap heap); 2126 2127 template JSLinearString* js::NewString<CanGC>(JSContext* cx, 2128 UniqueLatin1Chars chars, 2129 size_t length, gc::Heap heap); 2130 2131 template JSLinearString* js::NewString<NoGC>(JSContext* cx, 2132 UniqueLatin1Chars chars, 2133 size_t length, gc::Heap heap); 2134 2135 namespace js { 2136 2137 template <AllowGC allowGC, typename CharT> 2138 JSLinearString* NewStringCopyNDontDeflateNonStaticValidLength(JSContext* cx, 2139 const CharT* s, 2140 size_t n, 2141 gc::Heap heap) { 2142 if (JSInlineString::lengthFits<CharT>(n)) { 2143 return NewInlineString<allowGC>(cx, mozilla::Range<const CharT>(s, n), 2144 heap); 2145 } 2146 2147 Rooted<JSString::OwnedChars<CharT>> news(cx, 2148 ::AllocChars<CharT>(cx, n, heap)); 2149 if (!news) { 2150 if (!allowGC) { 2151 cx->recoverFromOutOfMemory(); 2152 } 2153 return nullptr; 2154 } 2155 2156 PodCopy(news.data(), s, n); 2157 2158 return JSLinearString::newValidLength<allowGC, CharT>(cx, &news, heap); 2159 } 2160 2161 template JSLinearString* NewStringCopyNDontDeflateNonStaticValidLength<CanGC>( 2162 JSContext* cx, const char16_t* s, size_t n, gc::Heap heap); 2163 2164 template JSLinearString* NewStringCopyNDontDeflateNonStaticValidLength<NoGC>( 2165 JSContext* cx, const char16_t* s, size_t n, gc::Heap heap); 2166 2167 template JSLinearString* NewStringCopyNDontDeflateNonStaticValidLength<CanGC>( 2168 JSContext* cx, const Latin1Char* s, size_t n, gc::Heap heap); 2169 2170 template JSLinearString* NewStringCopyNDontDeflateNonStaticValidLength<NoGC>( 2171 JSContext* cx, const Latin1Char* s, size_t n, gc::Heap heap); 2172 2173 template <AllowGC allowGC, typename CharT> 2174 JSLinearString* NewStringCopyNDontDeflate(JSContext* cx, const CharT* s, 2175 size_t n, gc::Heap heap) { 2176 if (JSLinearString* str = TryEmptyOrStaticString(cx, s, n)) { 2177 return str; 2178 } 2179 2180 if (MOZ_UNLIKELY(!JSLinearString::validateLength(cx, n))) { 2181 return nullptr; 2182 } 2183 2184 return NewStringCopyNDontDeflateNonStaticValidLength<allowGC>(cx, s, n, heap); 2185 } 2186 2187 template JSLinearString* NewStringCopyNDontDeflate<CanGC>(JSContext* cx, 2188 const char16_t* s, 2189 size_t n, 2190 gc::Heap heap); 2191 2192 template JSLinearString* NewStringCopyNDontDeflate<NoGC>(JSContext* cx, 2193 const char16_t* s, 2194 size_t n, 2195 gc::Heap heap); 2196 2197 template JSLinearString* NewStringCopyNDontDeflate<CanGC>(JSContext* cx, 2198 const Latin1Char* s, 2199 size_t n, 2200 gc::Heap heap); 2201 2202 template JSLinearString* NewStringCopyNDontDeflate<NoGC>(JSContext* cx, 2203 const Latin1Char* s, 2204 size_t n, 2205 gc::Heap heap); 2206 2207 JSLinearString* NewLatin1StringZ(JSContext* cx, UniqueChars chars, 2208 gc::Heap heap) { 2209 size_t length = strlen(chars.get()); 2210 UniqueLatin1Chars latin1(reinterpret_cast<Latin1Char*>(chars.release())); 2211 return NewString<CanGC>(cx, std::move(latin1), length, heap); 2212 } 2213 2214 template <AllowGC allowGC, typename CharT> 2215 JSLinearString* NewStringCopyN(JSContext* cx, const CharT* s, size_t n, 2216 gc::Heap heap) { 2217 if constexpr (std::is_same_v<CharT, char16_t>) { 2218 if (CanStoreCharsAsLatin1(s, n)) { 2219 return NewStringDeflated<allowGC>(cx, s, n, heap); 2220 } 2221 } 2222 2223 return NewStringCopyNDontDeflate<allowGC>(cx, s, n, heap); 2224 } 2225 2226 template JSLinearString* NewStringCopyN<CanGC>(JSContext* cx, const char16_t* s, 2227 size_t n, gc::Heap heap); 2228 2229 template JSLinearString* NewStringCopyN<NoGC>(JSContext* cx, const char16_t* s, 2230 size_t n, gc::Heap heap); 2231 2232 template JSLinearString* NewStringCopyN<CanGC>(JSContext* cx, 2233 const Latin1Char* s, size_t n, 2234 gc::Heap heap); 2235 2236 template JSLinearString* NewStringCopyN<NoGC>(JSContext* cx, 2237 const Latin1Char* s, size_t n, 2238 gc::Heap heap); 2239 2240 template <typename CharT> 2241 JSAtom* NewAtomCopyNDontDeflateValidLength(JSContext* cx, const CharT* s, 2242 size_t n, js::HashNumber hash) { 2243 if constexpr (std::is_same_v<CharT, char16_t>) { 2244 MOZ_ASSERT(!CanStoreCharsAsLatin1(s, n)); 2245 } 2246 2247 if (JSAtom::lengthFitsInline<CharT>(n)) { 2248 return NewInlineAtom(cx, s, n, hash); 2249 } 2250 2251 JSString::OwnedChars<CharT> newChars(AllocAtomCharsValidLength<CharT>(cx, n)); 2252 if (!newChars) { 2253 return nullptr; 2254 } 2255 2256 PodCopy(newChars.data(), s, n); 2257 2258 return JSAtom::newValidLength<CharT>(cx, newChars, hash); 2259 } 2260 2261 template JSAtom* NewAtomCopyNDontDeflateValidLength(JSContext* cx, 2262 const char16_t* s, size_t n, 2263 js::HashNumber hash); 2264 2265 template JSAtom* NewAtomCopyNDontDeflateValidLength(JSContext* cx, 2266 const Latin1Char* s, 2267 size_t n, 2268 js::HashNumber hash); 2269 2270 template <typename CharT> 2271 JSAtom* NewAtomCopyNMaybeDeflateValidLength(JSContext* cx, const CharT* s, 2272 size_t n, js::HashNumber hash) { 2273 if constexpr (std::is_same_v<CharT, char16_t>) { 2274 if (CanStoreCharsAsLatin1(s, n)) { 2275 return NewAtomDeflatedValidLength(cx, s, n, hash); 2276 } 2277 } 2278 2279 return NewAtomCopyNDontDeflateValidLength(cx, s, n, hash); 2280 } 2281 2282 template JSAtom* NewAtomCopyNMaybeDeflateValidLength(JSContext* cx, 2283 const char16_t* s, 2284 size_t n, 2285 js::HashNumber hash); 2286 2287 template JSAtom* NewAtomCopyNMaybeDeflateValidLength(JSContext* cx, 2288 const Latin1Char* s, 2289 size_t n, 2290 js::HashNumber hash); 2291 2292 JSLinearString* NewStringCopyUTF8N(JSContext* cx, const JS::UTF8Chars& utf8, 2293 JS::SmallestEncoding encoding, 2294 gc::Heap heap) { 2295 if (encoding == JS::SmallestEncoding::ASCII) { 2296 return NewStringCopyN<js::CanGC>(cx, utf8.begin().get(), utf8.length(), 2297 heap); 2298 } 2299 2300 size_t length; 2301 if (encoding == JS::SmallestEncoding::Latin1) { 2302 UniqueLatin1Chars latin1( 2303 UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::StringBufferArena) 2304 .get()); 2305 if (!latin1) { 2306 return nullptr; 2307 } 2308 2309 return NewString<js::CanGC>(cx, std::move(latin1), length, heap); 2310 } 2311 2312 MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16); 2313 2314 UniqueTwoByteChars utf16( 2315 UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::StringBufferArena) 2316 .get()); 2317 if (!utf16) { 2318 return nullptr; 2319 } 2320 2321 return NewString<js::CanGC>(cx, std::move(utf16), length, heap); 2322 } 2323 2324 JSLinearString* NewStringCopyUTF8N(JSContext* cx, const JS::UTF8Chars& utf8, 2325 gc::Heap heap) { 2326 JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8); 2327 return NewStringCopyUTF8N(cx, utf8, encoding, heap); 2328 } 2329 2330 template <typename CharT> 2331 MOZ_ALWAYS_INLINE JSLinearString* ExternalStringCache::lookupImpl( 2332 const CharT* chars, size_t len) const { 2333 AutoCheckCannotGC nogc; 2334 2335 for (size_t i = 0; i < NumEntries; i++) { 2336 JSLinearString* str = entries_[i]; 2337 if (!str || str->length() != len) { 2338 continue; 2339 } 2340 2341 if constexpr (std::is_same_v<CharT, JS::Latin1Char>) { 2342 if (!str->hasLatin1Chars()) { 2343 continue; 2344 } 2345 } else { 2346 if (!str->hasTwoByteChars()) { 2347 continue; 2348 } 2349 } 2350 2351 const CharT* strChars = str->chars<CharT>(nogc); 2352 if (chars == strChars) { 2353 // Note that we don't need an incremental barrier here or below. 2354 // The cache is purged on GC so any string we get from the cache 2355 // must have been allocated after the GC started. 2356 MOZ_ASSERT(!str->isInline()); 2357 return str; 2358 } 2359 2360 // Compare the chars. Don't do this for long strings as it will be 2361 // faster to allocate a new external string. 2362 static const size_t MaxLengthForCharComparison = 100; 2363 if (len <= MaxLengthForCharComparison && EqualChars(chars, strChars, len)) { 2364 return str; 2365 } 2366 } 2367 2368 return nullptr; 2369 } 2370 2371 MOZ_ALWAYS_INLINE JSLinearString* ExternalStringCache::lookup( 2372 const JS::Latin1Char* chars, size_t len) const { 2373 return lookupImpl(chars, len); 2374 } 2375 MOZ_ALWAYS_INLINE JSLinearString* ExternalStringCache::lookup( 2376 const char16_t* chars, size_t len) const { 2377 return lookupImpl(chars, len); 2378 } 2379 2380 MOZ_ALWAYS_INLINE void ExternalStringCache::put(JSLinearString* str) { 2381 for (size_t i = NumEntries - 1; i > 0; i--) { 2382 entries_[i] = entries_[i - 1]; 2383 } 2384 entries_[0] = str; 2385 } 2386 2387 template <typename CharT> 2388 MOZ_ALWAYS_INLINE JSInlineString* ExternalStringCache::lookupInlineLatin1Impl( 2389 const CharT* chars, size_t len) const { 2390 MOZ_ASSERT(CanStoreCharsAsLatin1(chars, len)); 2391 MOZ_ASSERT(JSThinInlineString::lengthFits<Latin1Char>(len)); 2392 2393 AutoCheckCannotGC nogc; 2394 2395 for (size_t i = 0; i < NumEntries; i++) { 2396 JSInlineString* str = inlineLatin1Entries_[i]; 2397 if (!str || str->length() != len) { 2398 continue; 2399 } 2400 2401 const JS::Latin1Char* strChars = str->latin1Chars(nogc); 2402 if (EqualChars(chars, strChars, len)) { 2403 return str; 2404 } 2405 } 2406 2407 return nullptr; 2408 } 2409 2410 MOZ_ALWAYS_INLINE JSInlineString* ExternalStringCache::lookupInlineLatin1( 2411 const JS::Latin1Char* chars, size_t len) const { 2412 return lookupInlineLatin1Impl(chars, len); 2413 } 2414 MOZ_ALWAYS_INLINE JSInlineString* ExternalStringCache::lookupInlineLatin1( 2415 const char16_t* chars, size_t len) const { 2416 return lookupInlineLatin1Impl(chars, len); 2417 } 2418 2419 MOZ_ALWAYS_INLINE void ExternalStringCache::putInlineLatin1( 2420 JSInlineString* str) { 2421 MOZ_ASSERT(str->hasLatin1Chars()); 2422 2423 for (size_t i = NumEntries - 1; i > 0; i--) { 2424 inlineLatin1Entries_[i] = inlineLatin1Entries_[i - 1]; 2425 } 2426 inlineLatin1Entries_[0] = str; 2427 } 2428 2429 } /* namespace js */ 2430 2431 template <AllowGC allowGC> 2432 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringMaybeDeflated( 2433 JSContext* cx, const mozilla::Range<const JS::Latin1Char>& chars, 2434 gc::Heap heap = gc::Heap::Default) { 2435 return NewInlineString<allowGC>(cx, chars, heap); 2436 } 2437 2438 template <AllowGC allowGC> 2439 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringMaybeDeflated( 2440 JSContext* cx, const mozilla::Range<const char16_t>& chars, 2441 gc::Heap heap = gc::Heap::Default) { 2442 return NewInlineStringDeflated<allowGC>(cx, chars, heap); 2443 } 2444 2445 namespace js { 2446 2447 template <typename CharT> 2448 JSString* NewMaybeExternalString(JSContext* cx, const CharT* s, size_t n, 2449 const JSExternalStringCallbacks* callbacks, 2450 bool* allocatedExternal, gc::Heap heap) { 2451 if (JSString* str = TryEmptyOrStaticString(cx, s, n)) { 2452 *allocatedExternal = false; 2453 return str; 2454 } 2455 2456 ExternalStringCache& cache = cx->zone()->externalStringCache(); 2457 2458 if (JSThinInlineString::lengthFits<Latin1Char>(n) && 2459 CanStoreCharsAsLatin1(s, n)) { 2460 *allocatedExternal = false; 2461 if (JSInlineString* str = cache.lookupInlineLatin1(s, n)) { 2462 return str; 2463 } 2464 JSInlineString* str = NewInlineStringMaybeDeflated<AllowGC::CanGC>( 2465 cx, mozilla::Range<const CharT>(s, n), heap); 2466 if (!str) { 2467 return nullptr; 2468 } 2469 cache.putInlineLatin1(str); 2470 return str; 2471 } 2472 2473 if (auto* str = cache.lookup(s, n)) { 2474 *allocatedExternal = false; 2475 return str; 2476 } 2477 2478 JSExternalString* str = JSExternalString::new_(cx, s, n, callbacks); 2479 if (!str) { 2480 return nullptr; 2481 } 2482 2483 *allocatedExternal = true; 2484 cache.put(str); 2485 return str; 2486 } 2487 2488 template JSString* NewMaybeExternalString( 2489 JSContext* cx, const JS::Latin1Char* s, size_t n, 2490 const JSExternalStringCallbacks* callbacks, bool* allocatedExternal, 2491 gc::Heap heap); 2492 2493 template JSString* NewMaybeExternalString( 2494 JSContext* cx, const char16_t* s, size_t n, 2495 const JSExternalStringCallbacks* callbacks, bool* allocatedExternal, 2496 gc::Heap heap); 2497 2498 } /* namespace js */ 2499 2500 template <typename CharT, typename BufferT> 2501 static JSString* NewStringFromBuffer(JSContext* cx, BufferT&& buffer, 2502 size_t length) { 2503 AssertHeapIsIdle(); 2504 CHECK_THREAD(cx); 2505 2506 const auto* s = static_cast<const CharT*>(buffer->Data()); 2507 2508 if (JSString* str = TryEmptyOrStaticString(cx, s, length)) { 2509 return str; 2510 } 2511 2512 ExternalStringCache& cache = cx->zone()->externalStringCache(); 2513 2514 // Use the inline-string cache that we also use for external strings. 2515 if (JSThinInlineString::lengthFits<Latin1Char>(length) && 2516 CanStoreCharsAsLatin1(s, length)) { 2517 if (JSInlineString* str = cache.lookupInlineLatin1(s, length)) { 2518 return str; 2519 } 2520 JSInlineString* str = NewInlineStringMaybeDeflated<AllowGC::CanGC>( 2521 cx, mozilla::Range(s, length), gc::Heap::Default); 2522 if (!str) { 2523 return nullptr; 2524 } 2525 cache.putInlineLatin1(str); 2526 return str; 2527 } 2528 2529 if (auto* str = cache.lookup(s, length)) { 2530 return str; 2531 } 2532 2533 JSLinearString* str; 2534 if (JSInlineString::lengthFits<CharT>(length)) { 2535 str = NewInlineString<CanGC>(cx, mozilla::Range(s, length), 2536 gc::Heap::Default); 2537 } else { 2538 // Note: |buffer| is either a StringBuffer* or a RefPtr<StringBuffer>, so 2539 // ensure we have a RefPtr. 2540 RefPtr<mozilla::StringBuffer> bufferRef(std::move(buffer)); 2541 Rooted<JSString::OwnedChars<CharT>> owned(cx, std::move(bufferRef), length); 2542 str = JSLinearString::new_<CanGC, CharT>(cx, &owned, gc::Heap::Default); 2543 } 2544 if (!str) { 2545 return nullptr; 2546 } 2547 cache.put(str); 2548 return str; 2549 } 2550 2551 JS_PUBLIC_API JSString* JS::NewStringFromLatin1Buffer( 2552 JSContext* cx, RefPtr<mozilla::StringBuffer> buffer, size_t length) { 2553 return NewStringFromBuffer<Latin1Char>(cx, std::move(buffer), length); 2554 } 2555 2556 JS_PUBLIC_API JSString* JS::NewStringFromKnownLiveLatin1Buffer( 2557 JSContext* cx, mozilla::StringBuffer* buffer, size_t length) { 2558 return NewStringFromBuffer<Latin1Char>(cx, buffer, length); 2559 } 2560 2561 JS_PUBLIC_API JSString* JS::NewStringFromTwoByteBuffer( 2562 JSContext* cx, RefPtr<mozilla::StringBuffer> buffer, size_t length) { 2563 return NewStringFromBuffer<char16_t>(cx, std::move(buffer), length); 2564 } 2565 2566 JS_PUBLIC_API JSString* JS::NewStringFromKnownLiveTwoByteBuffer( 2567 JSContext* cx, mozilla::StringBuffer* buffer, size_t length) { 2568 return NewStringFromBuffer<char16_t>(cx, buffer, length); 2569 } 2570 2571 template <typename BufferT> 2572 static JSString* NewStringFromUTF8Buffer(JSContext* cx, BufferT&& buffer, 2573 size_t length) { 2574 AssertHeapIsIdle(); 2575 CHECK_THREAD(cx); 2576 2577 const JS::UTF8Chars utf8(static_cast<const char*>(buffer->Data()), length); 2578 2579 JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8); 2580 if (encoding == JS::SmallestEncoding::ASCII) { 2581 // ASCII case can use the string buffer as Latin1 buffer. 2582 return NewStringFromBuffer<Latin1Char>(cx, std::move(buffer), length); 2583 } 2584 2585 // Non-ASCII case cannot use the string buffer. 2586 return NewStringCopyUTF8N(cx, utf8, encoding); 2587 } 2588 2589 JS_PUBLIC_API JSString* JS::NewStringFromUTF8Buffer( 2590 JSContext* cx, RefPtr<mozilla::StringBuffer> buffer, size_t length) { 2591 return ::NewStringFromUTF8Buffer(cx, std::move(buffer), length); 2592 } 2593 2594 JS_PUBLIC_API JSString* JS::NewStringFromKnownLiveUTF8Buffer( 2595 JSContext* cx, mozilla::StringBuffer* buffer, size_t length) { 2596 return ::NewStringFromUTF8Buffer(cx, buffer, length); 2597 } 2598 2599 #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_CACHEIR_SPEW) 2600 void JSExtensibleString::dumpOwnRepresentationFields( 2601 js::JSONPrinter& json) const { 2602 json.property("capacity", capacity()); 2603 } 2604 2605 void JSInlineString::dumpOwnRepresentationFields(js::JSONPrinter& json) const {} 2606 2607 void JSLinearString::dumpOwnRepresentationFields(js::JSONPrinter& json) const { 2608 if (hasStringBuffer()) { 2609 # ifdef DEBUG 2610 json.property("bufferRefCount", stringBuffer()->RefCount()); 2611 # endif 2612 return; 2613 } 2614 if (!isInline()) { 2615 // Include whether the chars are in the nursery even for tenured 2616 // strings, which should always be false. For investigating bugs, it's 2617 // better to not assume that. 2618 js::Nursery& nursery = runtimeFromMainThread()->gc.nursery(); 2619 bool inNursery = nursery.isInside(nonInlineCharsRaw()); 2620 json.boolProperty("charsInNursery", inNursery); 2621 } 2622 } 2623 #endif 2624 2625 struct RepresentativeExternalString : public JSExternalStringCallbacks { 2626 void finalize(JS::Latin1Char* chars) const override { 2627 // Constant chars, nothing to do. 2628 } 2629 void finalize(char16_t* chars) const override { 2630 // Constant chars, nothing to do. 2631 } 2632 size_t sizeOfBuffer(const JS::Latin1Char* chars, 2633 mozilla::MallocSizeOf mallocSizeOf) const override { 2634 // This string's buffer is not heap-allocated, so its malloc size is 0. 2635 return 0; 2636 } 2637 size_t sizeOfBuffer(const char16_t* chars, 2638 mozilla::MallocSizeOf mallocSizeOf) const override { 2639 // This string's buffer is not heap-allocated, so its malloc size is 0. 2640 return 0; 2641 } 2642 }; 2643 2644 static const RepresentativeExternalString RepresentativeExternalStringCallbacks; 2645 2646 template <typename CheckString, typename CharT> 2647 static bool FillWithRepresentatives(JSContext* cx, Handle<ArrayObject*> array, 2648 uint32_t* index, const CharT* chars, 2649 size_t len, size_t inlineStringMaxLength, 2650 size_t inlineAtomMaxLength, 2651 const CheckString& check, gc::Heap heap) { 2652 auto AppendString = [&check](JSContext* cx, Handle<ArrayObject*> array, 2653 uint32_t* index, HandleString s) { 2654 MOZ_ASSERT(check(s)); 2655 (void)check; // silence clang -Wunused-lambda-capture in opt builds 2656 RootedValue val(cx, StringValue(s)); 2657 return JS_DefineElement(cx, array, (*index)++, val, 0); 2658 }; 2659 2660 MOZ_ASSERT(len > inlineStringMaxLength); 2661 MOZ_ASSERT(len > inlineAtomMaxLength); 2662 2663 // Normal atom. 2664 RootedString atom1(cx, AtomizeChars(cx, chars, len)); 2665 if (!atom1 || !AppendString(cx, array, index, atom1)) { 2666 return false; 2667 } 2668 MOZ_ASSERT(atom1->isAtom()); 2669 2670 // Thin inline atom. 2671 RootedString atom2(cx, AtomizeChars(cx, chars, 2)); 2672 if (!atom2 || !AppendString(cx, array, index, atom2)) { 2673 return false; 2674 } 2675 MOZ_ASSERT(atom2->isAtom()); 2676 MOZ_ASSERT(atom2->isInline()); 2677 2678 // Fat inline atom. 2679 RootedString atom3(cx, AtomizeChars(cx, chars, inlineAtomMaxLength)); 2680 if (!atom3 || !AppendString(cx, array, index, atom3)) { 2681 return false; 2682 } 2683 MOZ_ASSERT(atom3->isAtom()); 2684 MOZ_ASSERT_IF(inlineStringMaxLength < inlineAtomMaxLength, 2685 atom3->isFatInline()); 2686 2687 // Normal linear string; maybe nursery. 2688 RootedString linear1(cx, NewStringCopyN<CanGC>(cx, chars, len, heap)); 2689 if (!linear1 || !AppendString(cx, array, index, linear1)) { 2690 return false; 2691 } 2692 MOZ_ASSERT(linear1->isLinear()); 2693 2694 // Inline string; maybe nursery. 2695 RootedString linear2(cx, NewStringCopyN<CanGC>(cx, chars, 3, heap)); 2696 if (!linear2 || !AppendString(cx, array, index, linear2)) { 2697 return false; 2698 } 2699 MOZ_ASSERT(linear2->isLinear()); 2700 MOZ_ASSERT(linear2->isInline()); 2701 2702 // Fat inline string; maybe nursery. 2703 RootedString linear3( 2704 cx, NewStringCopyN<CanGC>(cx, chars, inlineStringMaxLength, heap)); 2705 if (!linear3 || !AppendString(cx, array, index, linear3)) { 2706 return false; 2707 } 2708 MOZ_ASSERT(linear3->isLinear()); 2709 MOZ_ASSERT(linear3->isFatInline()); 2710 2711 // Rope; maybe nursery. 2712 RootedString rope(cx, ConcatStrings<CanGC>(cx, atom1, atom3, heap)); 2713 if (!rope || !AppendString(cx, array, index, rope)) { 2714 return false; 2715 } 2716 MOZ_ASSERT(rope->isRope()); 2717 2718 // Dependent; maybe nursery. 2719 RootedString dep(cx, NewDependentString(cx, atom1, 0, len - 2, heap)); 2720 if (!dep || !AppendString(cx, array, index, dep)) { 2721 return false; 2722 } 2723 MOZ_ASSERT(dep->isDependent()); 2724 2725 // Extensible; maybe nursery. 2726 RootedString temp1(cx, NewStringCopyN<CanGC>(cx, chars, len, heap)); 2727 if (!temp1) { 2728 return false; 2729 } 2730 RootedString extensible(cx, ConcatStrings<CanGC>(cx, temp1, atom3, heap)); 2731 if (!extensible || !extensible->ensureLinear(cx)) { 2732 return false; 2733 } 2734 if (!AppendString(cx, array, index, extensible)) { 2735 return false; 2736 } 2737 MOZ_ASSERT(extensible->isExtensible()); 2738 2739 RootedString external1(cx), external2(cx); 2740 if constexpr (std::is_same_v<CharT, char16_t>) { 2741 external1 = JS_NewExternalUCString(cx, (const char16_t*)chars, len, 2742 &RepresentativeExternalStringCallbacks); 2743 if (!external1 || !AppendString(cx, array, index, external1)) { 2744 return false; 2745 } 2746 MOZ_ASSERT(external1->isExternal()); 2747 2748 external2 = JS_NewExternalUCString(cx, (const char16_t*)chars, 2, 2749 &RepresentativeExternalStringCallbacks); 2750 if (!external2 || !AppendString(cx, array, index, external2)) { 2751 return false; 2752 } 2753 MOZ_ASSERT(external2->isExternal()); 2754 } else { 2755 external1 = 2756 JS_NewExternalStringLatin1(cx, (const Latin1Char*)chars, len, 2757 &RepresentativeExternalStringCallbacks); 2758 if (!external1 || !AppendString(cx, array, index, external1)) { 2759 return false; 2760 } 2761 MOZ_ASSERT(external1->isExternal()); 2762 2763 external2 = 2764 JS_NewExternalStringLatin1(cx, (const Latin1Char*)chars, 2, 2765 &RepresentativeExternalStringCallbacks); 2766 if (!external2 || !AppendString(cx, array, index, external2)) { 2767 return false; 2768 } 2769 MOZ_ASSERT(external2->isExternal()); 2770 } 2771 2772 // Assert the strings still have the types we expect after creating the 2773 // other strings. 2774 2775 MOZ_ASSERT(atom1->isAtom()); 2776 MOZ_ASSERT(atom2->isAtom()); 2777 MOZ_ASSERT(atom3->isAtom()); 2778 MOZ_ASSERT(atom2->isInline()); 2779 MOZ_ASSERT_IF(inlineStringMaxLength < inlineAtomMaxLength, 2780 atom3->isFatInline()); 2781 2782 MOZ_ASSERT(linear1->isLinear()); 2783 MOZ_ASSERT(linear2->isLinear()); 2784 MOZ_ASSERT(linear3->isLinear()); 2785 MOZ_ASSERT(linear2->isInline()); 2786 MOZ_ASSERT(linear3->isFatInline()); 2787 2788 MOZ_ASSERT(rope->isRope()); 2789 MOZ_ASSERT(dep->isDependent()); 2790 MOZ_ASSERT(extensible->isExtensible()); 2791 MOZ_ASSERT(external1->isExternal()); 2792 MOZ_ASSERT(external2->isExternal()); 2793 return true; 2794 } 2795 2796 /* static */ 2797 bool JSString::fillWithRepresentatives(JSContext* cx, 2798 Handle<ArrayObject*> array) { 2799 uint32_t index = 0; 2800 2801 auto CheckTwoByte = [](JSString* str) { return str->hasTwoByteChars(); }; 2802 auto CheckLatin1 = [](JSString* str) { return str->hasLatin1Chars(); }; 2803 2804 static const char16_t twoByteChars[] = 2805 u"\u1234abc\0def\u5678ghijklmasdfa\0xyz0123456789"; 2806 static const Latin1Char latin1Chars[] = "abc\0defghijklmasdfa\0xyz0123456789"; 2807 2808 // Create strings using both the default heap and forcing the tenured heap. If 2809 // nursery strings are available, this is a best effort at creating them in 2810 // the default heap case. Since nursery strings may be disabled or a GC may 2811 // occur during this process, there may be duplicate representatives in the 2812 // final list. 2813 2814 if (!FillWithRepresentatives(cx, array, &index, twoByteChars, 2815 std::size(twoByteChars) - 1, 2816 JSFatInlineString::MAX_LENGTH_TWO_BYTE, 2817 js::FatInlineAtom::MAX_LENGTH_TWO_BYTE, 2818 CheckTwoByte, gc::Heap::Tenured)) { 2819 return false; 2820 } 2821 if (!FillWithRepresentatives(cx, array, &index, latin1Chars, 2822 std::size(latin1Chars) - 1, 2823 JSFatInlineString::MAX_LENGTH_LATIN1, 2824 js::FatInlineAtom::MAX_LENGTH_LATIN1, 2825 CheckLatin1, gc::Heap::Tenured)) { 2826 return false; 2827 } 2828 if (!FillWithRepresentatives(cx, array, &index, twoByteChars, 2829 std::size(twoByteChars) - 1, 2830 JSFatInlineString::MAX_LENGTH_TWO_BYTE, 2831 js::FatInlineAtom::MAX_LENGTH_TWO_BYTE, 2832 CheckTwoByte, gc::Heap::Default)) { 2833 return false; 2834 } 2835 if (!FillWithRepresentatives(cx, array, &index, latin1Chars, 2836 std::size(latin1Chars) - 1, 2837 JSFatInlineString::MAX_LENGTH_LATIN1, 2838 js::FatInlineAtom::MAX_LENGTH_LATIN1, 2839 CheckLatin1, gc::Heap::Default)) { 2840 return false; 2841 } 2842 2843 #ifdef DEBUG 2844 // * Normal atom 2845 // * Inline atom. 2846 // * Fat inline atom. 2847 // * Normal linear string 2848 // * Inline string 2849 // * Fat inline string 2850 // * Rope; maybe nursery. 2851 // * Dependent 2852 // * Extensible 2853 // * External with original len 2854 // * External with len==2 2855 static constexpr uint32_t StringTypes = 11; 2856 // * Latin1 2857 // * TwoByte 2858 static constexpr uint32_t CharTypes = 2; 2859 // * Tenured 2860 // * Default 2861 static constexpr uint32_t HeapType = 2; 2862 MOZ_ASSERT(index == StringTypes * CharTypes * HeapType); 2863 #endif 2864 2865 return true; 2866 } 2867 2868 bool JSString::tryReplaceWithAtomRef(JSAtom* atom) { 2869 MOZ_ASSERT(!isAtomRef()); 2870 2871 if (isDependedOn() || isInline() || isExternal()) { 2872 return false; 2873 } 2874 2875 AutoCheckCannotGC nogc; 2876 if (hasOutOfLineChars()) { 2877 if (asLinear().hasStringBuffer()) { 2878 // If the string is in the nursery, the reference to the buffer will be 2879 // released during the next minor GC (in Nursery::sweep). If the string is 2880 // tenured, we have to release this reference here. 2881 if (isTenured()) { 2882 RemoveCellMemory(this, allocSize(), MemoryUse::StringContents); 2883 asLinear().stringBuffer()->Release(); 2884 } 2885 } else { 2886 void* buffer = asLinear().nonInlineCharsRaw(); 2887 // This is a little cheeky and so deserves a comment. If the string is 2888 // not tenured, then either its buffer lives purely in the nursery, in 2889 // which case it will just be forgotten and blown away in the next 2890 // minor GC, or it is tracked in the nursery's mallocedBuffers hashtable, 2891 // in which case it will be freed for us in the next minor GC. We opt 2892 // to let the GC take care of it since there's a chance it will run 2893 // during idle time. 2894 if (isTenured()) { 2895 RemoveCellMemory(this, allocSize(), MemoryUse::StringContents); 2896 js_free(buffer); 2897 } 2898 } 2899 } 2900 2901 // Pre-barrier for d.s.u3 which is overwritten and d.s.u2 which is ignored 2902 // for atom refs. 2903 MOZ_ASSERT(isRope() || isLinear()); 2904 if (isRope()) { 2905 PreWriteBarrier(d.s.u2.left); 2906 PreWriteBarrier(d.s.u3.right); 2907 } else if (isDependent()) { 2908 PreWriteBarrier(d.s.u3.base); 2909 } 2910 2911 uint32_t flags = INIT_ATOM_REF_FLAGS; 2912 d.s.u3.atom = atom; 2913 if (atom->hasLatin1Chars()) { 2914 flags |= LATIN1_CHARS_BIT; 2915 setLengthAndFlags(length(), flags); 2916 setNonInlineChars(atom->chars<Latin1Char>(nogc), atom->hasStringBuffer()); 2917 } else { 2918 setLengthAndFlags(length(), flags); 2919 setNonInlineChars(atom->chars<char16_t>(nogc), atom->hasStringBuffer()); 2920 } 2921 // Redundant, but just a reminder that this needs to be true or else we need 2922 // to check and conditionally put ourselves in the store buffer 2923 MOZ_ASSERT(atom->isTenured()); 2924 return true; 2925 } 2926 2927 template <typename CharT> 2928 bool js::StringChars<CharT>::maybeAlloc(JSContext* cx, size_t length, 2929 gc::Heap heap) { 2930 assertValidRequest(0, length); 2931 2932 if (JSInlineString::lengthFits<CharT>(length)) { 2933 return true; 2934 } 2935 2936 if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) { 2937 return false; 2938 } 2939 2940 auto chars = AllocChars<CharT>(cx, length, heap); 2941 if (!chars) { 2942 return false; 2943 } 2944 2945 ownedChars_.set(std::move(chars)); 2946 return true; 2947 } 2948 2949 template bool js::StringChars<JS::Latin1Char>::maybeAlloc(JSContext*, size_t, 2950 gc::Heap); 2951 template bool js::StringChars<char16_t>::maybeAlloc(JSContext*, size_t, 2952 gc::Heap); 2953 2954 template <typename CharT> 2955 bool js::StringChars<CharT>::maybeRealloc(JSContext* cx, size_t oldLength, 2956 size_t newLength, gc::Heap heap) { 2957 assertValidRequest(oldLength, newLength); 2958 2959 // Nothing to do if new length still fits into inline storage. 2960 if (JSInlineString::lengthFits<CharT>(newLength)) { 2961 return true; 2962 } 2963 2964 if (MOZ_UNLIKELY(!JSString::validateLength(cx, newLength))) { 2965 return false; 2966 } 2967 2968 // Unused |ownedChars_| means we were previously using inlining storage. 2969 if (!ownedChars_) { 2970 auto chars = AllocChars<CharT>(cx, newLength, heap); 2971 if (!chars) { 2972 return false; 2973 } 2974 std::memcpy(chars.data(), inlineChars_, InlineLength * sizeof(CharT)); 2975 2976 ownedChars_.set(std::move(chars)); 2977 return true; 2978 } 2979 2980 // Use |realloc| for malloc'ed characters. 2981 if (ownedChars_.isMalloced()) { 2982 CharT* oldChars = ownedChars_.release(); 2983 CharT* newChars = cx->pod_arena_realloc(js::StringBufferArena, oldChars, 2984 oldLength, newLength); 2985 if (!newChars) { 2986 js_free(oldChars); 2987 return false; 2988 } 2989 2990 using Kind = typename JSString::OwnedChars<CharT>::Kind; 2991 ownedChars_.set({newChars, newLength, Kind::Malloc}); 2992 return true; 2993 } 2994 2995 // Use |StringBuffer::Realloc| for string buffers. 2996 if (ownedChars_.hasStringBuffer()) { 2997 static_assert( 2998 mozilla::StringBuffer::IsValidLength<CharT>(JSString::MAX_LENGTH), 2999 "JSString length must be valid for StringBuffer"); 3000 3001 auto* oldBuffer = mozilla::StringBuffer::FromData(ownedChars_.release()); 3002 3003 // Note: StringBuffers must be null-terminated. 3004 auto* newBuffer = mozilla::StringBuffer::Realloc( 3005 oldBuffer, (newLength + 1) * sizeof(CharT), 3006 mozilla::Some(js::StringBufferArena)); 3007 if (!newBuffer) { 3008 oldBuffer->Release(); 3009 ReportOutOfMemory(cx); 3010 return false; 3011 } 3012 auto* newChars = static_cast<CharT*>(newBuffer->Data()); 3013 newChars[newLength] = '\0'; 3014 3015 using Kind = typename JSString::OwnedChars<CharT>::Kind; 3016 ownedChars_.set({newChars, newLength, Kind::StringBuffer}); 3017 3018 MOZ_ASSERT(newBuffer->RefCount() == 1); 3019 return true; 3020 } 3021 3022 // Keep the previous nursery allocated chars alive. 3023 Rooted<JSString::OwnedChars<CharT>> oldOwnedChars( 3024 cx, std::move(ownedChars_.get())); 3025 3026 // Nursery allocated characters can't be reallocated, so perform a new 3027 // allocation instead. 3028 auto chars = AllocChars<CharT>(cx, newLength, heap); 3029 if (!chars) { 3030 return false; 3031 } 3032 mozilla::PodCopy(chars.data(), oldOwnedChars.data(), oldLength); 3033 3034 ownedChars_.set(std::move(chars)); 3035 return true; 3036 } 3037 3038 template bool js::StringChars<JS::Latin1Char>::maybeRealloc(JSContext*, size_t, 3039 size_t, gc::Heap); 3040 template bool js::StringChars<char16_t>::maybeRealloc(JSContext*, size_t, 3041 size_t, gc::Heap); 3042 3043 template <typename CharT> 3044 template <AllowGC allowGC> 3045 JSLinearString* js::StringChars<CharT>::toStringDontDeflate(JSContext* cx, 3046 size_t length, 3047 gc::Heap heap) { 3048 MOZ_ASSERT(length == lastRequestedLength_); 3049 3050 if (JSInlineString::lengthFits<CharT>(length)) { 3051 MOZ_ASSERT(!ownedChars_, 3052 "unexpected OwnedChars allocation for inline strings"); 3053 if (auto* str = TryEmptyOrStaticString(cx, inlineChars_, length)) { 3054 return str; 3055 } 3056 return NewInlineString<CanGC>(cx, inlineChars_, length, heap); 3057 } 3058 3059 MOZ_ASSERT(ownedChars_, 3060 "missing OwnedChars allocation for non-inline strings"); 3061 MOZ_ASSERT(length == ownedChars_.length(), 3062 "requested length doesn't match allocation"); 3063 return JSLinearString::newValidLength<allowGC, CharT>(cx, &ownedChars_, heap); 3064 } 3065 3066 template JSLinearString* 3067 js::StringChars<JS::Latin1Char>::toStringDontDeflate<CanGC>(JSContext*, size_t, 3068 gc::Heap); 3069 template JSLinearString* js::StringChars<char16_t>::toStringDontDeflate<CanGC>( 3070 JSContext*, size_t, gc::Heap); 3071 template JSLinearString* 3072 js::StringChars<JS::Latin1Char>::toStringDontDeflate<NoGC>(JSContext*, size_t, 3073 gc::Heap); 3074 template JSLinearString* js::StringChars<char16_t>::toStringDontDeflate<NoGC>( 3075 JSContext*, size_t, gc::Heap); 3076 3077 template <typename CharT> 3078 template <AllowGC allowGC> 3079 JSLinearString* js::StringChars<CharT>::toStringDontDeflateNonStatic( 3080 JSContext* cx, size_t length, gc::Heap heap) { 3081 MOZ_ASSERT(length == lastRequestedLength_); 3082 3083 if (JSInlineString::lengthFits<CharT>(length)) { 3084 MOZ_ASSERT(!ownedChars_, 3085 "unexpected OwnedChars allocation for inline strings"); 3086 MOZ_ASSERT(!TryEmptyOrStaticString(cx, inlineChars_, length), 3087 "unexpected static string found"); 3088 return NewInlineString<allowGC>(cx, inlineChars_, length, heap); 3089 } 3090 3091 MOZ_ASSERT(ownedChars_, 3092 "missing OwnedChars allocation for non-inline strings"); 3093 MOZ_ASSERT(length == ownedChars_.length(), 3094 "requested length doesn't match allocation"); 3095 return JSLinearString::newValidLength<allowGC, CharT>(cx, &ownedChars_, heap); 3096 } 3097 3098 template JSLinearString* 3099 js::StringChars<JS::Latin1Char>::toStringDontDeflateNonStatic<CanGC>(JSContext*, 3100 size_t, 3101 gc::Heap); 3102 template JSLinearString* 3103 js::StringChars<char16_t>::toStringDontDeflateNonStatic<CanGC>(JSContext*, 3104 size_t, 3105 gc::Heap); 3106 template JSLinearString* 3107 js::StringChars<JS::Latin1Char>::toStringDontDeflateNonStatic<NoGC>(JSContext*, 3108 size_t, 3109 gc::Heap); 3110 template JSLinearString* 3111 js::StringChars<char16_t>::toStringDontDeflateNonStatic<NoGC>(JSContext*, 3112 size_t, gc::Heap); 3113 3114 template <typename CharT> 3115 bool js::AtomStringChars<CharT>::maybeAlloc(JSContext* cx, size_t length) { 3116 assertValidRequest(0, length); 3117 3118 if (JSInlineString::lengthFits<CharT>(length)) { 3119 return true; 3120 } 3121 3122 if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) { 3123 return false; 3124 } 3125 3126 mallocChars_ = cx->make_pod_arena_array<CharT>(js::StringBufferArena, length); 3127 return !!mallocChars_; 3128 } 3129 3130 template bool js::AtomStringChars<JS::Latin1Char>::maybeAlloc(JSContext*, 3131 size_t); 3132 template bool js::AtomStringChars<char16_t>::maybeAlloc(JSContext*, size_t); 3133 3134 template <typename CharT> 3135 JSAtom* js::AtomStringChars<CharT>::toAtom(JSContext* cx, size_t length) { 3136 MOZ_ASSERT(length == lastRequestedLength_); 3137 return AtomizeChars(cx, data(), length); 3138 } 3139 3140 template JSAtom* js::AtomStringChars<JS::Latin1Char>::toAtom(JSContext*, 3141 size_t); 3142 template JSAtom* js::AtomStringChars<char16_t>::toAtom(JSContext*, size_t); 3143 3144 /*** Conversions ************************************************************/ 3145 3146 UniqueChars js::EncodeLatin1(JSContext* cx, JSString* str) { 3147 JSLinearString* linear = str->ensureLinear(cx); 3148 if (!linear) { 3149 return nullptr; 3150 } 3151 3152 JS::AutoCheckCannotGC nogc; 3153 if (linear->hasTwoByteChars()) { 3154 JS::Latin1CharsZ chars = 3155 JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->twoByteRange(nogc)); 3156 return UniqueChars(chars.c_str()); 3157 } 3158 3159 size_t len = str->length(); 3160 Latin1Char* buf = cx->pod_malloc<Latin1Char>(len + 1); 3161 if (!buf) { 3162 return nullptr; 3163 } 3164 3165 PodCopy(buf, linear->latin1Chars(nogc), len); 3166 buf[len] = '\0'; 3167 3168 return UniqueChars(reinterpret_cast<char*>(buf)); 3169 } 3170 3171 UniqueChars js::EncodeAscii(JSContext* cx, JSString* str) { 3172 JSLinearString* linear = str->ensureLinear(cx); 3173 if (!linear) { 3174 return nullptr; 3175 } 3176 3177 MOZ_ASSERT(StringIsAscii(linear)); 3178 return EncodeLatin1(cx, linear); 3179 } 3180 3181 UniqueChars js::IdToPrintableUTF8(JSContext* cx, HandleId id, 3182 IdToPrintableBehavior behavior) { 3183 // ToString(<symbol>) throws a TypeError, therefore require that callers 3184 // request source representation when |id| is a property key. 3185 MOZ_ASSERT_IF(behavior == IdToPrintableBehavior::IdIsIdentifier, 3186 id.isAtom() && IsIdentifierNameOrPrivateName(id.toAtom())); 3187 3188 RootedValue v(cx, IdToValue(id)); 3189 JSString* str; 3190 if (behavior == IdToPrintableBehavior::IdIsPropertyKey) { 3191 str = ValueToSource(cx, v); 3192 } else { 3193 str = ToString<CanGC>(cx, v); 3194 } 3195 if (!str) { 3196 return nullptr; 3197 } 3198 return StringToNewUTF8CharsZ(cx, *str); 3199 } 3200 3201 template <AllowGC allowGC> 3202 JSString* js::ToStringSlow( 3203 JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg) { 3204 /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */ 3205 MOZ_ASSERT(!arg.isString()); 3206 3207 Value v = arg; 3208 if (!v.isPrimitive()) { 3209 if (!allowGC) { 3210 return nullptr; 3211 } 3212 RootedValue v2(cx, v); 3213 if (!ToPrimitive(cx, JSTYPE_STRING, &v2)) { 3214 return nullptr; 3215 } 3216 v = v2; 3217 } 3218 3219 JSString* str; 3220 if (v.isString()) { 3221 str = v.toString(); 3222 } else if (v.isInt32()) { 3223 str = Int32ToString<allowGC>(cx, v.toInt32()); 3224 } else if (v.isDouble()) { 3225 str = NumberToString<allowGC>(cx, v.toDouble()); 3226 } else if (v.isBoolean()) { 3227 str = BooleanToString(cx, v.toBoolean()); 3228 } else if (v.isNull()) { 3229 str = cx->names().null; 3230 } else if (v.isSymbol()) { 3231 if (allowGC) { 3232 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3233 JSMSG_SYMBOL_TO_STRING); 3234 } 3235 return nullptr; 3236 } else if (v.isBigInt()) { 3237 if (!allowGC) { 3238 return nullptr; 3239 } 3240 RootedBigInt i(cx, v.toBigInt()); 3241 str = BigInt::toString<CanGC>(cx, i, 10); 3242 } else { 3243 MOZ_ASSERT(v.isUndefined()); 3244 str = cx->names().undefined; 3245 } 3246 return str; 3247 } 3248 3249 template JSString* js::ToStringSlow<CanGC>(JSContext* cx, HandleValue arg); 3250 3251 template JSString* js::ToStringSlow<NoGC>(JSContext* cx, const Value& arg); 3252 3253 JS_PUBLIC_API JSString* js::ToStringSlow(JSContext* cx, HandleValue v) { 3254 return ToStringSlow<CanGC>(cx, v); 3255 }