1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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/FloatingPoint.h"
10 #include "mozilla/MathAlgorithms.h"
11 #include "mozilla/MemoryReporting.h"
12 #include "mozilla/PodOperations.h"
13 #include "mozilla/RangedPtr.h"
14 #include "mozilla/TypeTraits.h"
15 #include "mozilla/Unused.h"
16
17 #include "gc/Marking.h"
18 #include "gc/Nursery.h"
19 #include "js/UbiNode.h"
20 #include "util/StringBuffer.h"
21 #include "vm/GeckoProfiler.h"
22
23 #include "vm/GeckoProfiler-inl.h"
24 #include "vm/JSCompartment-inl.h"
25 #include "vm/JSContext-inl.h"
26 #include "vm/JSObject-inl.h"
27
28 using namespace js;
29
30 using mozilla::IsNegativeZero;
31 using mozilla::IsSame;
32 using mozilla::PodCopy;
33 using mozilla::PodEqual;
34 using mozilla::RangedPtr;
35 using mozilla::RoundUpPow2;
36 using mozilla::Unused;
37
38 using JS::AutoCheckCannotGC;
39
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)40 size_t JSString::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
41 // JSRope: do nothing, we'll count all children chars when we hit the leaf
42 // strings.
43 if (isRope()) return 0;
44
45 MOZ_ASSERT(isLinear());
46
47 // JSDependentString: do nothing, we'll count the chars when we hit the base
48 // string.
49 if (isDependent()) return 0;
50
51 // JSExternalString: Ask the embedding to tell us what's going on. If it
52 // doesn't want to say, don't count, the chars could be stored anywhere.
53 if (isExternal()) {
54 if (auto* cb = runtimeFromActiveCooperatingThread()
55 ->externalStringSizeofCallback.ref()) {
56 // Our callback isn't supposed to cause GC.
57 JS::AutoSuppressGCAnalysis nogc;
58 return cb(this, mallocSizeOf);
59 }
60 return 0;
61 }
62
63 MOZ_ASSERT(isFlat());
64
65 // JSExtensibleString: count the full capacity, not just the used space.
66 if (isExtensible()) {
67 JSExtensibleString& extensible = asExtensible();
68 return extensible.hasLatin1Chars()
69 ? mallocSizeOf(extensible.rawLatin1Chars())
70 : mallocSizeOf(extensible.rawTwoByteChars());
71 }
72
73 // JSInlineString, JSFatInlineString [JSInlineAtom, JSFatInlineAtom]: the
74 // chars are inline.
75 if (isInline()) return 0;
76
77 // JSAtom, JSUndependedString: measure the space for the chars. For
78 // JSUndependedString, there is no need to count the base string, for the
79 // same reason as JSDependentString above.
80 JSFlatString& flat = asFlat();
81 return flat.hasLatin1Chars() ? mallocSizeOf(flat.rawLatin1Chars())
82 : mallocSizeOf(flat.rawTwoByteChars());
83 }
84
size(mozilla::MallocSizeOf mallocSizeOf) const85 JS::ubi::Node::Size JS::ubi::Concrete<JSString>::size(
86 mozilla::MallocSizeOf mallocSizeOf) const {
87 JSString& str = get();
88 size_t size;
89 if (str.isAtom())
90 size =
91 str.isFatInline() ? sizeof(js::FatInlineAtom) : sizeof(js::NormalAtom);
92 else
93 size = str.isFatInline() ? sizeof(JSFatInlineString) : sizeof(JSString);
94
95 if (IsInsideNursery(&str)) size += Nursery::stringHeaderSize();
96
97 size += str.sizeOfExcludingThis(mallocSizeOf);
98
99 return size;
100 }
101
102 const char16_t JS::ubi::Concrete<JSString>::concreteTypeName[] = u"JSString";
103
104 #ifdef DEBUG
105
106 template <typename CharT>
dumpChars(const CharT * s,size_t n,js::GenericPrinter & out)107 /*static */ void JSString::dumpChars(const CharT* s, size_t n,
108 js::GenericPrinter& out) {
109 if (n == SIZE_MAX) {
110 n = 0;
111 while (s[n]) n++;
112 }
113
114 out.put("\"");
115 for (size_t i = 0; i < n; i++) {
116 char16_t c = s[i];
117 if (c == '\n')
118 out.put("\\n");
119 else if (c == '\t')
120 out.put("\\t");
121 else if (c >= 32 && c < 127)
122 out.putChar((char)s[i]);
123 else if (c <= 255)
124 out.printf("\\x%02x", unsigned(c));
125 else
126 out.printf("\\u%04x", unsigned(c));
127 }
128 out.putChar('"');
129 }
130
131 template void JSString::dumpChars(const Latin1Char* s, size_t n,
132 js::GenericPrinter& out);
133
134 template void JSString::dumpChars(const char16_t* s, size_t n,
135 js::GenericPrinter& out);
136
dumpCharsNoNewline(js::GenericPrinter & out)137 void JSString::dumpCharsNoNewline(js::GenericPrinter& out) {
138 if (JSLinearString* linear = ensureLinear(nullptr)) {
139 AutoCheckCannotGC nogc;
140 if (hasLatin1Chars())
141 dumpChars(linear->latin1Chars(nogc), length(), out);
142 else
143 dumpChars(linear->twoByteChars(nogc), length(), out);
144 } else {
145 out.put("(oom in JSString::dumpCharsNoNewline)");
146 }
147 }
148
dump()149 void JSString::dump() {
150 js::Fprinter out(stderr);
151 dump(out);
152 }
153
dump(js::GenericPrinter & out)154 void JSString::dump(js::GenericPrinter& out) {
155 dumpNoNewline(out);
156 out.putChar('\n');
157 }
158
dumpNoNewline(js::GenericPrinter & out)159 void JSString::dumpNoNewline(js::GenericPrinter& out) {
160 if (JSLinearString* linear = ensureLinear(nullptr)) {
161 AutoCheckCannotGC nogc;
162 if (hasLatin1Chars()) {
163 const Latin1Char* chars = linear->latin1Chars(nogc);
164 out.printf("JSString* (%p) = Latin1Char * (%p) = ", (void*)this,
165 (void*)chars);
166 dumpChars(chars, length(), out);
167 } else {
168 const char16_t* chars = linear->twoByteChars(nogc);
169 out.printf("JSString* (%p) = char16_t * (%p) = ", (void*)this,
170 (void*)chars);
171 dumpChars(chars, length(), out);
172 }
173 } else {
174 out.put("(oom in JSString::dump)");
175 }
176 }
177
dumpRepresentation(js::GenericPrinter & out,int indent) const178 void JSString::dumpRepresentation(js::GenericPrinter& out, int indent) const {
179 if (isRope())
180 asRope().dumpRepresentation(out, indent);
181 else if (isDependent())
182 asDependent().dumpRepresentation(out, indent);
183 else if (isExternal())
184 asExternal().dumpRepresentation(out, indent);
185 else if (isExtensible())
186 asExtensible().dumpRepresentation(out, indent);
187 else if (isInline())
188 asInline().dumpRepresentation(out, indent);
189 else if (isFlat())
190 asFlat().dumpRepresentation(out, indent);
191 else
192 MOZ_CRASH("Unexpected JSString representation");
193 }
194
dumpRepresentationHeader(js::GenericPrinter & out,const char * subclass) const195 void JSString::dumpRepresentationHeader(js::GenericPrinter& out,
196 const char* subclass) const {
197 uint32_t flags = d.u1.flags;
198 // Print the string's address as an actual C++ expression, to facilitate
199 // copy-and-paste into a debugger.
200 out.printf("((%s*) %p) length: %zu flags: 0x%x", subclass, this, length(),
201 flags);
202 if (flags & LINEAR_BIT) out.put(" LINEAR");
203 if (flags & HAS_BASE_BIT) out.put(" HAS_BASE");
204 if (flags & INLINE_CHARS_BIT) out.put(" INLINE_CHARS");
205 if (flags & NON_ATOM_BIT) out.put(" NON_ATOM");
206 if (isPermanentAtom()) out.put(" PERMANENT");
207 if (flags & LATIN1_CHARS_BIT) out.put(" LATIN1");
208 if (flags & INDEX_VALUE_BIT) out.printf(" INDEX_VALUE(%u)", getIndexValue());
209 if (!isTenured()) out.put(" NURSERY");
210 out.putChar('\n');
211 }
212
dumpRepresentationChars(js::GenericPrinter & out,int indent) const213 void JSLinearString::dumpRepresentationChars(js::GenericPrinter& out,
214 int indent) const {
215 if (hasLatin1Chars()) {
216 out.printf("%*schars: ((Latin1Char*) %p) ", indent, "", rawLatin1Chars());
217 dumpChars(rawLatin1Chars(), length(), out);
218 } else {
219 out.printf("%*schars: ((char16_t*) %p) ", indent, "", rawTwoByteChars());
220 dumpChars(rawTwoByteChars(), length(), out);
221 }
222 out.putChar('\n');
223 }
224
equals(const char * s)225 bool JSString::equals(const char* s) {
226 JSLinearString* linear = ensureLinear(nullptr);
227 if (!linear) {
228 fprintf(stderr, "OOM in JSString::equals!\n");
229 return false;
230 }
231
232 return StringEqualsAscii(linear, s);
233 }
234 #endif /* DEBUG */
235
236 template <typename CharT>
AllocChars(JSString * str,size_t length,CharT ** chars,size_t * capacity)237 static MOZ_ALWAYS_INLINE bool AllocChars(JSString* str, size_t length,
238 CharT** chars, size_t* capacity) {
239 /*
240 * String length doesn't include the null char, so include it here before
241 * doubling. Adding the null char after doubling would interact poorly with
242 * round-up malloc schemes.
243 */
244 size_t numChars = length + 1;
245
246 /*
247 * Grow by 12.5% if the buffer is very large. Otherwise, round up to the
248 * next power of 2. This is similar to what we do with arrays; see
249 * JSObject::ensureDenseArrayElements.
250 */
251 static const size_t DOUBLING_MAX = 1024 * 1024;
252 numChars = numChars > DOUBLING_MAX ? numChars + (numChars / 8)
253 : RoundUpPow2(numChars);
254
255 /* Like length, capacity does not include the null char, so take it out. */
256 *capacity = numChars - 1;
257
258 JS_STATIC_ASSERT(JSString::MAX_LENGTH * sizeof(CharT) < UINT32_MAX);
259 *chars = str->zone()->pod_malloc<CharT>(numChars);
260 return *chars != nullptr;
261 }
262
copyLatin1CharsZ(JSContext * cx,ScopedJSFreePtr<Latin1Char> & out) const263 bool JSRope::copyLatin1CharsZ(JSContext* cx,
264 ScopedJSFreePtr<Latin1Char>& out) const {
265 return copyCharsInternal<Latin1Char>(cx, out, true);
266 }
267
copyTwoByteCharsZ(JSContext * cx,ScopedJSFreePtr<char16_t> & out) const268 bool JSRope::copyTwoByteCharsZ(JSContext* cx,
269 ScopedJSFreePtr<char16_t>& out) const {
270 return copyCharsInternal<char16_t>(cx, out, true);
271 }
272
copyLatin1Chars(JSContext * cx,ScopedJSFreePtr<Latin1Char> & out) const273 bool JSRope::copyLatin1Chars(JSContext* cx,
274 ScopedJSFreePtr<Latin1Char>& out) const {
275 return copyCharsInternal<Latin1Char>(cx, out, false);
276 }
277
copyTwoByteChars(JSContext * cx,ScopedJSFreePtr<char16_t> & out) const278 bool JSRope::copyTwoByteChars(JSContext* cx,
279 ScopedJSFreePtr<char16_t>& out) const {
280 return copyCharsInternal<char16_t>(cx, out, false);
281 }
282
283 template <typename CharT>
copyCharsInternal(JSContext * cx,ScopedJSFreePtr<CharT> & out,bool nullTerminate) const284 bool JSRope::copyCharsInternal(JSContext* cx, ScopedJSFreePtr<CharT>& out,
285 bool nullTerminate) const {
286 /*
287 * Perform non-destructive post-order traversal of the rope, splatting
288 * each node's characters into a contiguous buffer.
289 */
290
291 size_t n = length();
292 if (cx)
293 out.reset(cx->pod_malloc<CharT>(n + 1));
294 else
295 out.reset(js_pod_malloc<CharT>(n + 1));
296
297 if (!out) return false;
298
299 Vector<const JSString*, 8, SystemAllocPolicy> nodeStack;
300 const JSString* str = this;
301 CharT* pos = out;
302 while (true) {
303 if (str->isRope()) {
304 if (!nodeStack.append(str->asRope().rightChild())) return false;
305 str = str->asRope().leftChild();
306 } else {
307 CopyChars(pos, str->asLinear());
308 pos += str->length();
309 if (nodeStack.empty()) break;
310 str = nodeStack.popCopy();
311 }
312 }
313
314 MOZ_ASSERT(pos == out + n);
315
316 if (nullTerminate) out[n] = 0;
317
318 return true;
319 }
320
321 #ifdef DEBUG
dumpRepresentation(js::GenericPrinter & out,int indent) const322 void JSRope::dumpRepresentation(js::GenericPrinter& out, int indent) const {
323 dumpRepresentationHeader(out, "JSRope");
324 indent += 2;
325
326 out.printf("%*sleft: ", indent, "");
327 leftChild()->dumpRepresentation(out, indent);
328
329 out.printf("%*sright: ", indent, "");
330 rightChild()->dumpRepresentation(out, indent);
331 }
332 #endif
333
334 namespace js {
335
336 template <>
CopyChars(char16_t * dest,const JSLinearString & str)337 void CopyChars(char16_t* dest, const JSLinearString& str) {
338 AutoCheckCannotGC nogc;
339 if (str.hasTwoByteChars())
340 PodCopy(dest, str.twoByteChars(nogc), str.length());
341 else
342 CopyAndInflateChars(dest, str.latin1Chars(nogc), str.length());
343 }
344
345 template <>
CopyChars(Latin1Char * dest,const JSLinearString & str)346 void CopyChars(Latin1Char* dest, const JSLinearString& str) {
347 AutoCheckCannotGC nogc;
348 if (str.hasLatin1Chars()) {
349 PodCopy(dest, str.latin1Chars(nogc), str.length());
350 } else {
351 /*
352 * When we flatten a TwoByte rope, we turn child ropes (including Latin1
353 * ropes) into TwoByte dependent strings. If one of these strings is
354 * also part of another Latin1 rope tree, we can have a Latin1 rope with
355 * a TwoByte descendent and we end up here when we flatten it. Although
356 * the chars are stored as TwoByte, we know they must be in the Latin1
357 * range, so we can safely deflate here.
358 */
359 size_t len = str.length();
360 const char16_t* chars = str.twoByteChars(nogc);
361 for (size_t i = 0; i < len; i++) {
362 MOZ_ASSERT(chars[i] <= JSString::MAX_LATIN1_CHAR);
363 dest[i] = chars[i];
364 }
365 }
366 }
367
368 } /* namespace js */
369
370 template <JSRope::UsingBarrier b, typename CharT>
flattenInternal(JSContext * maybecx)371 JSFlatString* JSRope::flattenInternal(JSContext* maybecx) {
372 /*
373 * Consider the DAG of JSRopes rooted at this JSRope, with non-JSRopes as
374 * its leaves. Mutate the root JSRope into a JSExtensibleString containing
375 * the full flattened text that the root represents, and mutate all other
376 * JSRopes in the interior of the DAG into JSDependentStrings that refer to
377 * this new JSExtensibleString.
378 *
379 * If the leftmost leaf of our DAG is a JSExtensibleString, consider
380 * stealing its buffer for use in our new root, and transforming it into a
381 * JSDependentString too. Do not mutate any of the other leaves.
382 *
383 * Perform a depth-first dag traversal, splatting each node's characters
384 * into a contiguous buffer. Visit each rope node three times:
385 * 1. record position in the buffer and recurse into left child;
386 * 2. recurse into the right child;
387 * 3. transform the node into a dependent string.
388 * To avoid maintaining a stack, tree nodes are mutated to indicate how many
389 * times they have been visited. Since ropes can be dags, a node may be
390 * encountered multiple times during traversal. However, step 3 above leaves
391 * a valid dependent string, so everything works out.
392 *
393 * While ropes avoid all sorts of quadratic cases with string concatenation,
394 * they can't help when ropes are immediately flattened. One idiomatic case
395 * that we'd like to keep linear (and has traditionally been linear in SM
396 * and other JS engines) is:
397 *
398 * while (...) {
399 * s += ...
400 * s.flatten
401 * }
402 *
403 * Two behaviors accomplish this:
404 *
405 * - When the leftmost non-rope in the DAG we're flattening is a
406 * JSExtensibleString with sufficient capacity to hold the entire
407 * flattened string, we just flatten the DAG into its buffer. Then, when
408 * we transform the root of the DAG from a JSRope into a
409 * JSExtensibleString, we steal that buffer, and change the victim from a
410 * JSExtensibleString to a JSDependentString. In this case, the left-hand
411 * side of the string never needs to be copied.
412 *
413 * - Otherwise, we round up the total flattened size and create a fresh
414 * JSExtensibleString with that much capacity. If this in turn becomes the
415 * leftmost leaf of a subsequent flatten, we will hopefully be able to
416 * fill it, as in the case above.
417 *
418 * Note that, even though the code for creating JSDependentStrings avoids
419 * creating dependents of dependents, we can create that situation here: the
420 * JSExtensibleStrings we transform into JSDependentStrings might have
421 * JSDependentStrings pointing to them already. Stealing the buffer doesn't
422 * change its address, only its owning JSExtensibleString, so all chars()
423 * pointers in the JSDependentStrings are still valid.
424 */
425 const size_t wholeLength = length();
426 size_t wholeCapacity;
427 CharT* wholeChars;
428 JSString* str = this;
429 CharT* pos;
430
431 /*
432 * JSString::flattenData is a tagged pointer to the parent node.
433 * The tag indicates what to do when we return to the parent.
434 */
435 static const uintptr_t Tag_Mask = 0x3;
436 static const uintptr_t Tag_FinishNode = 0x0;
437 static const uintptr_t Tag_VisitRightChild = 0x1;
438
439 AutoCheckCannotGC nogc;
440
441 /* Find the left most string, containing the first string. */
442 JSRope* leftMostRope = this;
443 while (leftMostRope->leftChild()->isRope())
444 leftMostRope = &leftMostRope->leftChild()->asRope();
445
446 if (leftMostRope->leftChild()->isExtensible()) {
447 JSExtensibleString& left = leftMostRope->leftChild()->asExtensible();
448 size_t capacity = left.capacity();
449 if (capacity >= wholeLength &&
450 left.hasTwoByteChars() == IsSame<CharT, char16_t>::value) {
451 wholeChars = const_cast<CharT*>(left.nonInlineChars<CharT>(nogc));
452 wholeCapacity = capacity;
453
454 /*
455 * Simulate a left-most traversal from the root to leftMost->leftChild()
456 * via first_visit_node
457 */
458 MOZ_ASSERT(str->isRope());
459 while (str != leftMostRope) {
460 if (b == WithIncrementalBarrier) {
461 JSString::writeBarrierPre(str->d.s.u2.left);
462 JSString::writeBarrierPre(str->d.s.u3.right);
463 }
464 JSString* child = str->d.s.u2.left;
465 js::BarrierMethods<JSString*>::postBarrier(&str->d.s.u2.left, child,
466 nullptr);
467 MOZ_ASSERT(child->isRope());
468 str->setNonInlineChars(wholeChars);
469 child->d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
470 str = child;
471 }
472 if (b == WithIncrementalBarrier) {
473 JSString::writeBarrierPre(str->d.s.u2.left);
474 JSString::writeBarrierPre(str->d.s.u3.right);
475 }
476 str->setNonInlineChars(wholeChars);
477 pos = wholeChars + left.d.u1.length;
478 if (IsSame<CharT, char16_t>::value)
479 left.d.u1.flags = DEPENDENT_FLAGS;
480 else
481 left.d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT;
482 left.d.s.u3.base = (JSLinearString*)this; /* will be true on exit */
483 BarrierMethods<JSString*>::postBarrier((JSString**)&left.d.s.u3.base,
484 nullptr, this);
485 Nursery& nursery = zone()->group()->nursery();
486 if (isTenured() && !left.isTenured())
487 nursery.removeMallocedBuffer(wholeChars);
488 else if (!isTenured() && left.isTenured())
489 nursery.registerMallocedBuffer(wholeChars);
490 goto visit_right_child;
491 }
492 }
493
494 if (!AllocChars(this, wholeLength, &wholeChars, &wholeCapacity)) {
495 if (maybecx) ReportOutOfMemory(maybecx);
496 return nullptr;
497 }
498
499 if (!isTenured()) {
500 Nursery& nursery = zone()->group()->nursery();
501 if (!nursery.registerMallocedBuffer(wholeChars)) {
502 js_free(wholeChars);
503 if (maybecx) ReportOutOfMemory(maybecx);
504 return nullptr;
505 }
506 }
507
508 pos = wholeChars;
509 first_visit_node : {
510 if (b == WithIncrementalBarrier) {
511 JSString::writeBarrierPre(str->d.s.u2.left);
512 JSString::writeBarrierPre(str->d.s.u3.right);
513 }
514
515 JSString& left = *str->d.s.u2.left;
516 js::BarrierMethods<JSString*>::postBarrier(&str->d.s.u2.left, &left, nullptr);
517 str->setNonInlineChars(pos);
518 if (left.isRope()) {
519 /* Return to this node when 'left' done, then goto visit_right_child. */
520 left.d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
521 str = &left;
522 goto first_visit_node;
523 }
524 CopyChars(pos, left.asLinear());
525 pos += left.length();
526 }
527 visit_right_child : {
528 JSString& right = *str->d.s.u3.right;
529 BarrierMethods<JSString*>::postBarrier(&str->d.s.u3.right, &right, nullptr);
530 if (right.isRope()) {
531 /* Return to this node when 'right' done, then goto finish_node. */
532 right.d.u1.flattenData = uintptr_t(str) | Tag_FinishNode;
533 str = &right;
534 goto first_visit_node;
535 }
536 CopyChars(pos, right.asLinear());
537 pos += right.length();
538 }
539
540 finish_node : {
541 if (str == this) {
542 MOZ_ASSERT(pos == wholeChars + wholeLength);
543 *pos = '\0';
544 str->d.u1.length = wholeLength;
545 if (IsSame<CharT, char16_t>::value)
546 str->d.u1.flags = EXTENSIBLE_FLAGS;
547 else
548 str->d.u1.flags = EXTENSIBLE_FLAGS | LATIN1_CHARS_BIT;
549 str->setNonInlineChars(wholeChars);
550 str->d.s.u3.capacity = wholeCapacity;
551 return &this->asFlat();
552 }
553 uintptr_t flattenData = str->d.u1.flattenData;
554 if (IsSame<CharT, char16_t>::value)
555 str->d.u1.flags = DEPENDENT_FLAGS;
556 else
557 str->d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT;
558 str->d.u1.length = pos - str->asLinear().nonInlineChars<CharT>(nogc);
559 str->d.s.u3.base = (JSLinearString*)this; /* will be true on exit */
560 BarrierMethods<JSString*>::postBarrier((JSString**)&str->d.s.u3.base, nullptr,
561 this);
562 str = (JSString*)(flattenData & ~Tag_Mask);
563 if ((flattenData & Tag_Mask) == Tag_VisitRightChild) goto visit_right_child;
564 MOZ_ASSERT((flattenData & Tag_Mask) == Tag_FinishNode);
565 goto finish_node;
566 }
567 }
568
569 template <JSRope::UsingBarrier b>
flattenInternal(JSContext * maybecx)570 JSFlatString* JSRope::flattenInternal(JSContext* maybecx) {
571 if (hasTwoByteChars()) return flattenInternal<b, char16_t>(maybecx);
572 return flattenInternal<b, Latin1Char>(maybecx);
573 }
574
flatten(JSContext * maybecx)575 JSFlatString* JSRope::flatten(JSContext* maybecx) {
576 mozilla::Maybe<AutoGeckoProfilerEntry> entry;
577 if (maybecx && !maybecx->helperThread())
578 entry.emplace(maybecx, "JSRope::flatten");
579
580 if (zone()->needsIncrementalBarrier())
581 return flattenInternal<WithIncrementalBarrier>(maybecx);
582 return flattenInternal<NoBarrier>(maybecx);
583 }
584
585 template <AllowGC allowGC>
EnsureLinear(JSContext * cx,typename MaybeRooted<JSString *,allowGC>::HandleType string)586 static JSLinearString* EnsureLinear(
587 JSContext* cx,
588 typename MaybeRooted<JSString*, allowGC>::HandleType string) {
589 JSLinearString* linear = string->ensureLinear(cx);
590 // Don't report an exception if GC is not allowed, just return nullptr.
591 if (!linear && !allowGC) cx->recoverFromOutOfMemory();
592 return linear;
593 }
594
595 template <AllowGC allowGC>
ConcatStrings(JSContext * cx,typename MaybeRooted<JSString *,allowGC>::HandleType left,typename MaybeRooted<JSString *,allowGC>::HandleType right)596 JSString* js::ConcatStrings(
597 JSContext* cx, typename MaybeRooted<JSString*, allowGC>::HandleType left,
598 typename MaybeRooted<JSString*, allowGC>::HandleType right) {
599 MOZ_ASSERT_IF(!left->isAtom(), cx->isInsideCurrentZone(left));
600 MOZ_ASSERT_IF(!right->isAtom(), cx->isInsideCurrentZone(right));
601
602 size_t leftLen = left->length();
603 if (leftLen == 0) return right;
604
605 size_t rightLen = right->length();
606 if (rightLen == 0) return left;
607
608 size_t wholeLength = leftLen + rightLen;
609 if (MOZ_UNLIKELY(wholeLength > JSString::MAX_LENGTH)) {
610 // Don't report an exception if GC is not allowed, just return nullptr.
611 if (allowGC) js::ReportAllocationOverflow(cx);
612 return nullptr;
613 }
614
615 bool isLatin1 = left->hasLatin1Chars() && right->hasLatin1Chars();
616 bool canUseInline = isLatin1
617 ? JSInlineString::lengthFits<Latin1Char>(wholeLength)
618 : JSInlineString::lengthFits<char16_t>(wholeLength);
619 if (canUseInline) {
620 Latin1Char* latin1Buf = nullptr; // initialize to silence GCC warning
621 char16_t* twoByteBuf = nullptr; // initialize to silence GCC warning
622 JSInlineString* str =
623 isLatin1 ? AllocateInlineString<allowGC>(cx, wholeLength, &latin1Buf)
624 : AllocateInlineString<allowGC>(cx, wholeLength, &twoByteBuf);
625 if (!str) return nullptr;
626
627 AutoCheckCannotGC nogc;
628 JSLinearString* leftLinear = EnsureLinear<allowGC>(cx, left);
629 if (!leftLinear) return nullptr;
630 JSLinearString* rightLinear = EnsureLinear<allowGC>(cx, right);
631 if (!rightLinear) return nullptr;
632
633 if (isLatin1) {
634 PodCopy(latin1Buf, leftLinear->latin1Chars(nogc), leftLen);
635 PodCopy(latin1Buf + leftLen, rightLinear->latin1Chars(nogc), rightLen);
636 latin1Buf[wholeLength] = 0;
637 } else {
638 if (leftLinear->hasTwoByteChars())
639 PodCopy(twoByteBuf, leftLinear->twoByteChars(nogc), leftLen);
640 else
641 CopyAndInflateChars(twoByteBuf, leftLinear->latin1Chars(nogc), leftLen);
642 if (rightLinear->hasTwoByteChars())
643 PodCopy(twoByteBuf + leftLen, rightLinear->twoByteChars(nogc),
644 rightLen);
645 else
646 CopyAndInflateChars(twoByteBuf + leftLen,
647 rightLinear->latin1Chars(nogc), rightLen);
648 twoByteBuf[wholeLength] = 0;
649 }
650
651 return str;
652 }
653
654 return JSRope::new_<allowGC>(cx, left, right, wholeLength);
655 }
656
657 template JSString* js::ConcatStrings<CanGC>(JSContext* cx, HandleString left,
658 HandleString right);
659
660 template JSString* js::ConcatStrings<NoGC>(JSContext* cx, JSString* const& left,
661 JSString* const& right);
662
663 template <typename CharT>
undependInternal(JSContext * cx)664 JSFlatString* JSDependentString::undependInternal(JSContext* cx) {
665 size_t n = length();
666 CharT* s = cx->pod_malloc<CharT>(n + 1);
667 if (!s) return nullptr;
668
669 if (!isTenured()) {
670 if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s)) {
671 js_free(s);
672 ReportOutOfMemory(cx);
673 return nullptr;
674 }
675 }
676
677 AutoCheckCannotGC nogc;
678 PodCopy(s, nonInlineChars<CharT>(nogc), n);
679 s[n] = '\0';
680 setNonInlineChars<CharT>(s);
681
682 /*
683 * Transform *this into an undepended string so 'base' will remain rooted
684 * for the benefit of any other dependent string that depends on *this.
685 */
686 if (IsSame<CharT, Latin1Char>::value)
687 d.u1.flags = UNDEPENDED_FLAGS | LATIN1_CHARS_BIT;
688 else
689 d.u1.flags = UNDEPENDED_FLAGS;
690
691 return &this->asFlat();
692 }
693
undepend(JSContext * cx)694 JSFlatString* JSDependentString::undepend(JSContext* cx) {
695 MOZ_ASSERT(JSString::isDependent());
696 return hasLatin1Chars() ? undependInternal<Latin1Char>(cx)
697 : undependInternal<char16_t>(cx);
698 }
699
700 #ifdef DEBUG
dumpRepresentation(js::GenericPrinter & out,int indent) const701 void JSDependentString::dumpRepresentation(js::GenericPrinter& out,
702 int indent) const {
703 dumpRepresentationHeader(out, "JSDependentString");
704 indent += 2;
705
706 if (mozilla::Maybe<size_t> offset = baseOffset())
707 out.printf("%*soffset: %zu\n", indent, "", *offset);
708
709 out.printf("%*sbase: ", indent, "");
710 base()->dumpRepresentation(out, indent);
711 }
712 #endif
713
EqualChars(JSLinearString * str1,JSLinearString * str2)714 bool js::EqualChars(JSLinearString* str1, JSLinearString* str2) {
715 MOZ_ASSERT(str1->length() == str2->length());
716
717 size_t len = str1->length();
718
719 AutoCheckCannotGC nogc;
720 if (str1->hasTwoByteChars()) {
721 if (str2->hasTwoByteChars())
722 return PodEqual(str1->twoByteChars(nogc), str2->twoByteChars(nogc), len);
723
724 return EqualChars(str2->latin1Chars(nogc), str1->twoByteChars(nogc), len);
725 }
726
727 if (str2->hasLatin1Chars())
728 return PodEqual(str1->latin1Chars(nogc), str2->latin1Chars(nogc), len);
729
730 return EqualChars(str1->latin1Chars(nogc), str2->twoByteChars(nogc), len);
731 }
732
HasSubstringAt(JSLinearString * text,JSLinearString * pat,size_t start)733 bool js::HasSubstringAt(JSLinearString* text, JSLinearString* pat,
734 size_t start) {
735 MOZ_ASSERT(start + pat->length() <= text->length());
736
737 size_t patLen = pat->length();
738
739 AutoCheckCannotGC nogc;
740 if (text->hasLatin1Chars()) {
741 const Latin1Char* textChars = text->latin1Chars(nogc) + start;
742 if (pat->hasLatin1Chars())
743 return PodEqual(textChars, pat->latin1Chars(nogc), patLen);
744
745 return EqualChars(textChars, pat->twoByteChars(nogc), patLen);
746 }
747
748 const char16_t* textChars = text->twoByteChars(nogc) + start;
749 if (pat->hasTwoByteChars())
750 return PodEqual(textChars, pat->twoByteChars(nogc), patLen);
751
752 return EqualChars(pat->latin1Chars(nogc), textChars, patLen);
753 }
754
EqualStrings(JSContext * cx,JSString * str1,JSString * str2,bool * result)755 bool js::EqualStrings(JSContext* cx, JSString* str1, JSString* str2,
756 bool* result) {
757 if (str1 == str2) {
758 *result = true;
759 return true;
760 }
761
762 size_t length1 = str1->length();
763 if (length1 != str2->length()) {
764 *result = false;
765 return true;
766 }
767
768 JSLinearString* linear1 = str1->ensureLinear(cx);
769 if (!linear1) return false;
770 JSLinearString* linear2 = str2->ensureLinear(cx);
771 if (!linear2) return false;
772
773 *result = EqualChars(linear1, linear2);
774 return true;
775 }
776
EqualStrings(JSLinearString * str1,JSLinearString * str2)777 bool js::EqualStrings(JSLinearString* str1, JSLinearString* str2) {
778 if (str1 == str2) return true;
779
780 size_t length1 = str1->length();
781 if (length1 != str2->length()) return false;
782
783 return EqualChars(str1, str2);
784 }
785
CompareChars(const char16_t * s1,size_t len1,JSLinearString * s2)786 int32_t js::CompareChars(const char16_t* s1, size_t len1, JSLinearString* s2) {
787 AutoCheckCannotGC nogc;
788 return s2->hasLatin1Chars()
789 ? CompareChars(s1, len1, s2->latin1Chars(nogc), s2->length())
790 : CompareChars(s1, len1, s2->twoByteChars(nogc), s2->length());
791 }
792
CompareStringsImpl(JSLinearString * str1,JSLinearString * str2)793 static int32_t CompareStringsImpl(JSLinearString* str1, JSLinearString* str2) {
794 size_t len1 = str1->length();
795 size_t len2 = str2->length();
796
797 AutoCheckCannotGC nogc;
798 if (str1->hasLatin1Chars()) {
799 const Latin1Char* chars1 = str1->latin1Chars(nogc);
800 return str2->hasLatin1Chars()
801 ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2)
802 : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2);
803 }
804
805 const char16_t* chars1 = str1->twoByteChars(nogc);
806 return str2->hasLatin1Chars()
807 ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2)
808 : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2);
809 }
810
CompareStrings(JSContext * cx,JSString * str1,JSString * str2,int32_t * result)811 bool js::CompareStrings(JSContext* cx, JSString* str1, JSString* str2,
812 int32_t* result) {
813 MOZ_ASSERT(str1);
814 MOZ_ASSERT(str2);
815
816 if (str1 == str2) {
817 *result = 0;
818 return true;
819 }
820
821 JSLinearString* linear1 = str1->ensureLinear(cx);
822 if (!linear1) return false;
823
824 JSLinearString* linear2 = str2->ensureLinear(cx);
825 if (!linear2) return false;
826
827 *result = CompareStringsImpl(linear1, linear2);
828 return true;
829 }
830
CompareAtoms(JSAtom * atom1,JSAtom * atom2)831 int32_t js::CompareAtoms(JSAtom* atom1, JSAtom* atom2) {
832 return CompareStringsImpl(atom1, atom2);
833 }
834
StringEqualsAscii(JSLinearString * str,const char * asciiBytes)835 bool js::StringEqualsAscii(JSLinearString* str, const char* asciiBytes) {
836 size_t length = strlen(asciiBytes);
837 #ifdef DEBUG
838 for (size_t i = 0; i != length; ++i)
839 MOZ_ASSERT(unsigned(asciiBytes[i]) <= 127);
840 #endif
841 if (length != str->length()) return false;
842
843 const Latin1Char* latin1 = reinterpret_cast<const Latin1Char*>(asciiBytes);
844
845 AutoCheckCannotGC nogc;
846 return str->hasLatin1Chars()
847 ? PodEqual(latin1, str->latin1Chars(nogc), length)
848 : EqualChars(latin1, str->twoByteChars(nogc), length);
849 }
850
851 template <typename CharT>
isIndexSlow(const CharT * s,size_t length,uint32_t * indexp)852 /* static */ bool JSFlatString::isIndexSlow(const CharT* s, size_t length,
853 uint32_t* indexp) {
854 CharT ch = *s;
855
856 if (!JS7_ISDEC(ch)) return false;
857
858 if (length > UINT32_CHAR_BUFFER_LENGTH) return false;
859
860 /*
861 * Make sure to account for the '\0' at the end of characters, dereferenced
862 * in the loop below.
863 */
864 RangedPtr<const CharT> cp(s, length + 1);
865 const RangedPtr<const CharT> end(s + length, s, length + 1);
866
867 uint32_t index = JS7_UNDEC(*cp++);
868 uint32_t oldIndex = 0;
869 uint32_t c = 0;
870
871 if (index != 0) {
872 while (JS7_ISDEC(*cp)) {
873 oldIndex = index;
874 c = JS7_UNDEC(*cp);
875 index = 10 * index + c;
876 cp++;
877 }
878 }
879
880 /* It's not an element if there are characters after the number. */
881 if (cp != end) return false;
882
883 /*
884 * Look out for "4294967296" and larger-number strings that fit in
885 * UINT32_CHAR_BUFFER_LENGTH: only unsigned 32-bit integers shall pass.
886 */
887 if (oldIndex < UINT32_MAX / 10 ||
888 (oldIndex == UINT32_MAX / 10 && c <= (UINT32_MAX % 10))) {
889 *indexp = index;
890 return true;
891 }
892
893 return false;
894 }
895
896 template bool JSFlatString::isIndexSlow(const Latin1Char* s, size_t length,
897 uint32_t* indexp);
898
899 template bool JSFlatString::isIndexSlow(const char16_t* s, size_t length,
900 uint32_t* indexp);
901
902 /*
903 * Set up some tools to make it easier to generate large tables. After constant
904 * folding, for each n, Rn(0) is the comma-separated list R(0), R(1), ...,
905 * R(2^n-1). Similary, Rn(k) (for any k and n) generates the list R(k), R(k+1),
906 * ..., R(k+2^n-1). To use this, define R appropriately, then use Rn(0) (for
907 * some value of n), then undefine R.
908 */
909 #define R2(n) R(n), R((n) + (1 << 0)), R((n) + (2 << 0)), R((n) + (3 << 0))
910 #define R4(n) R2(n), R2((n) + (1 << 2)), R2((n) + (2 << 2)), R2((n) + (3 << 2))
911 #define R6(n) R4(n), R4((n) + (1 << 4)), R4((n) + (2 << 4)), R4((n) + (3 << 4))
912 #define R7(n) R6(n), R6((n) + (1 << 6))
913
914 /*
915 * This is used when we generate our table of short strings, so the compiler is
916 * happier if we use |c| as few times as possible.
917 */
918 // clang-format off
919 #define FROM_SMALL_CHAR(c) Latin1Char((c) + ((c) < 10 ? '0' : \
920 (c) < 36 ? 'a' - 10 : \
921 'A' - 36))
922 // clang-format on
923
924 /*
925 * Declare length-2 strings. We only store strings where both characters are
926 * alphanumeric. The lower 10 short chars are the numerals, the next 26 are
927 * the lowercase letters, and the next 26 are the uppercase letters.
928 */
929 // clang-format off
930 #define TO_SMALL_CHAR(c) ((c) >= '0' && (c) <= '9' ? (c) - '0' : \
931 (c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 10 : \
932 (c) >= 'A' && (c) <= 'Z' ? (c) - 'A' + 36 : \
933 StaticStrings::INVALID_SMALL_CHAR)
934 // clang-format on
935
936 #define R TO_SMALL_CHAR
937 const StaticStrings::SmallChar StaticStrings::toSmallChar[] = {R7(0)};
938 #undef R
939
940 #undef R2
941 #undef R4
942 #undef R6
943 #undef R7
944
init(JSContext * cx)945 bool StaticStrings::init(JSContext* cx) {
946 AutoLockForExclusiveAccess lock(cx);
947 AutoAtomsCompartment ac(cx, lock);
948
949 static_assert(UNIT_STATIC_LIMIT - 1 <= JSString::MAX_LATIN1_CHAR,
950 "Unit strings must fit in Latin1Char.");
951
952 using Latin1Range = mozilla::Range<const Latin1Char>;
953
954 for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) {
955 Latin1Char buffer[] = {Latin1Char(i), '\0'};
956 JSFlatString* s = NewInlineString<NoGC>(cx, Latin1Range(buffer, 1));
957 if (!s) return false;
958 HashNumber hash = mozilla::HashString(buffer, 1);
959 unitStaticTable[i] = s->morphAtomizedStringIntoPermanentAtom(hash);
960 }
961
962 for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) {
963 Latin1Char buffer[] = {FROM_SMALL_CHAR(i >> 6), FROM_SMALL_CHAR(i & 0x3F),
964 '\0'};
965 JSFlatString* s = NewInlineString<NoGC>(cx, Latin1Range(buffer, 2));
966 if (!s) return false;
967 HashNumber hash = mozilla::HashString(buffer, 2);
968 length2StaticTable[i] = s->morphAtomizedStringIntoPermanentAtom(hash);
969 }
970
971 for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) {
972 if (i < 10) {
973 intStaticTable[i] = unitStaticTable[i + '0'];
974 } else if (i < 100) {
975 size_t index = ((size_t)TO_SMALL_CHAR((i / 10) + '0') << 6) +
976 TO_SMALL_CHAR((i % 10) + '0');
977 intStaticTable[i] = length2StaticTable[index];
978 } else {
979 Latin1Char buffer[] = {Latin1Char('0' + (i / 100)),
980 Latin1Char('0' + ((i / 10) % 10)),
981 Latin1Char('0' + (i % 10)), '\0'};
982 JSFlatString* s = NewInlineString<NoGC>(cx, Latin1Range(buffer, 3));
983 if (!s) return false;
984 HashNumber hash = mozilla::HashString(buffer, 3);
985 intStaticTable[i] = s->morphAtomizedStringIntoPermanentAtom(hash);
986 }
987
988 // Static string initialization can not race, so allow even without the
989 // lock.
990 intStaticTable[i]->maybeInitializeIndex(i, true);
991 }
992
993 return true;
994 }
995
trace(JSTracer * trc)996 void StaticStrings::trace(JSTracer* trc) {
997 /* These strings never change, so barriers are not needed. */
998
999 for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++)
1000 TraceProcessGlobalRoot(trc, unitStaticTable[i], "unit-static-string");
1001
1002 for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++)
1003 TraceProcessGlobalRoot(trc, length2StaticTable[i], "length2-static-string");
1004
1005 /* This may mark some strings more than once, but so be it. */
1006 for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++)
1007 TraceProcessGlobalRoot(trc, intStaticTable[i], "int-static-string");
1008 }
1009
1010 template <typename CharT>
isStatic(const CharT * chars,size_t length)1011 /* static */ bool StaticStrings::isStatic(const CharT* chars, size_t length) {
1012 switch (length) {
1013 case 1: {
1014 char16_t c = chars[0];
1015 return c < UNIT_STATIC_LIMIT;
1016 }
1017 case 2:
1018 return fitsInSmallChar(chars[0]) && fitsInSmallChar(chars[1]);
1019 case 3:
1020 if ('1' <= chars[0] && chars[0] <= '9' && '0' <= chars[1] &&
1021 chars[1] <= '9' && '0' <= chars[2] && chars[2] <= '9') {
1022 int i =
1023 (chars[0] - '0') * 100 + (chars[1] - '0') * 10 + (chars[2] - '0');
1024
1025 return unsigned(i) < INT_STATIC_LIMIT;
1026 }
1027 return false;
1028 default:
1029 return false;
1030 }
1031 }
1032
isStatic(JSAtom * atom)1033 /* static */ bool StaticStrings::isStatic(JSAtom* atom) {
1034 AutoCheckCannotGC nogc;
1035 return atom->hasLatin1Chars()
1036 ? isStatic(atom->latin1Chars(nogc), atom->length())
1037 : isStatic(atom->twoByteChars(nogc), atom->length());
1038 }
1039
init(JSContext * cx,JSString * s)1040 bool AutoStableStringChars::init(JSContext* cx, JSString* s) {
1041 RootedLinearString linearString(cx, s->ensureLinear(cx));
1042 if (!linearString) return false;
1043
1044 MOZ_ASSERT(state_ == Uninitialized);
1045
1046 if (linearString->isExternal() && !linearString->ensureFlat(cx)) return false;
1047
1048 // If the chars are inline then we need to copy them since they may be moved
1049 // by a compacting GC.
1050 if (baseIsInline(linearString)) {
1051 return linearString->hasTwoByteChars() ? copyTwoByteChars(cx, linearString)
1052 : copyLatin1Chars(cx, linearString);
1053 }
1054
1055 if (linearString->hasLatin1Chars()) {
1056 state_ = Latin1;
1057 latin1Chars_ = linearString->rawLatin1Chars();
1058 } else {
1059 state_ = TwoByte;
1060 twoByteChars_ = linearString->rawTwoByteChars();
1061 }
1062
1063 s_ = linearString;
1064 return true;
1065 }
1066
initTwoByte(JSContext * cx,JSString * s)1067 bool AutoStableStringChars::initTwoByte(JSContext* cx, JSString* s) {
1068 RootedLinearString linearString(cx, s->ensureLinear(cx));
1069 if (!linearString) return false;
1070
1071 MOZ_ASSERT(state_ == Uninitialized);
1072
1073 if (linearString->hasLatin1Chars())
1074 return copyAndInflateLatin1Chars(cx, linearString);
1075
1076 if (linearString->isExternal() && !linearString->ensureFlat(cx)) return false;
1077
1078 // If the chars are inline then we need to copy them since they may be moved
1079 // by a compacting GC.
1080 if (baseIsInline(linearString)) return copyTwoByteChars(cx, linearString);
1081
1082 state_ = TwoByte;
1083 twoByteChars_ = linearString->rawTwoByteChars();
1084 s_ = linearString;
1085 return true;
1086 }
1087
baseIsInline(HandleLinearString linearString)1088 bool AutoStableStringChars::baseIsInline(HandleLinearString linearString) {
1089 JSString* base = linearString;
1090 while (base->isDependent()) base = base->asDependent().base();
1091 return base->isInline();
1092 }
1093
1094 template <typename T>
allocOwnChars(JSContext * cx,size_t count)1095 T* AutoStableStringChars::allocOwnChars(JSContext* cx, size_t count) {
1096 static_assert(
1097 InlineCapacity >= sizeof(JS::Latin1Char) *
1098 (JSFatInlineString::MAX_LENGTH_LATIN1 + 1) &&
1099 InlineCapacity >=
1100 sizeof(char16_t) * (JSFatInlineString::MAX_LENGTH_TWO_BYTE + 1),
1101 "InlineCapacity too small to hold fat inline strings");
1102
1103 static_assert((JSString::MAX_LENGTH &
1104 mozilla::tl::MulOverflowMask<sizeof(T)>::value) == 0,
1105 "Size calculation can overflow");
1106 MOZ_ASSERT(count <= (JSString::MAX_LENGTH + 1));
1107 size_t size = sizeof(T) * count;
1108
1109 ownChars_.emplace(cx);
1110 if (!ownChars_->resize(size)) {
1111 ownChars_.reset();
1112 return nullptr;
1113 }
1114
1115 return reinterpret_cast<T*>(ownChars_->begin());
1116 }
1117
copyAndInflateLatin1Chars(JSContext * cx,HandleLinearString linearString)1118 bool AutoStableStringChars::copyAndInflateLatin1Chars(
1119 JSContext* cx, HandleLinearString linearString) {
1120 char16_t* chars = allocOwnChars<char16_t>(cx, linearString->length() + 1);
1121 if (!chars) return false;
1122
1123 CopyAndInflateChars(chars, linearString->rawLatin1Chars(),
1124 linearString->length());
1125 chars[linearString->length()] = 0;
1126
1127 state_ = TwoByte;
1128 twoByteChars_ = chars;
1129 s_ = linearString;
1130 return true;
1131 }
1132
copyLatin1Chars(JSContext * cx,HandleLinearString linearString)1133 bool AutoStableStringChars::copyLatin1Chars(JSContext* cx,
1134 HandleLinearString linearString) {
1135 size_t length = linearString->length();
1136 JS::Latin1Char* chars = allocOwnChars<JS::Latin1Char>(cx, length + 1);
1137 if (!chars) return false;
1138
1139 PodCopy(chars, linearString->rawLatin1Chars(), length);
1140 chars[length] = 0;
1141
1142 state_ = Latin1;
1143 latin1Chars_ = chars;
1144 s_ = linearString;
1145 return true;
1146 }
1147
copyTwoByteChars(JSContext * cx,HandleLinearString linearString)1148 bool AutoStableStringChars::copyTwoByteChars(JSContext* cx,
1149 HandleLinearString linearString) {
1150 size_t length = linearString->length();
1151 char16_t* chars = allocOwnChars<char16_t>(cx, length + 1);
1152 if (!chars) return false;
1153
1154 PodCopy(chars, linearString->rawTwoByteChars(), length);
1155 chars[length] = 0;
1156
1157 state_ = TwoByte;
1158 twoByteChars_ = chars;
1159 s_ = linearString;
1160 return true;
1161 }
1162
ensureFlat(JSContext * cx)1163 JSFlatString* JSString::ensureFlat(JSContext* cx) {
1164 if (isFlat()) return &asFlat();
1165 if (isDependent()) return asDependent().undepend(cx);
1166 if (isRope()) return asRope().flatten(cx);
1167 return asExternal().ensureFlat(cx);
1168 }
1169
ensureFlat(JSContext * cx)1170 JSFlatString* JSExternalString::ensureFlat(JSContext* cx) {
1171 MOZ_ASSERT(hasTwoByteChars());
1172
1173 size_t n = length();
1174 char16_t* s = cx->pod_malloc<char16_t>(n + 1);
1175 if (!s) return nullptr;
1176
1177 if (!isTenured()) {
1178 if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s)) {
1179 js_free(s);
1180 ReportOutOfMemory(cx);
1181 return nullptr;
1182 }
1183 }
1184
1185 // Copy the chars before finalizing the string.
1186 {
1187 AutoCheckCannotGC nogc;
1188 PodCopy(s, nonInlineChars<char16_t>(nogc), n);
1189 s[n] = '\0';
1190 }
1191
1192 // Release the external chars.
1193 finalize(cx->runtime()->defaultFreeOp());
1194
1195 // Transform the string into a non-external, flat string. Note that the
1196 // resulting string will still be in an AllocKind::EXTERNAL_STRING arena,
1197 // but will no longer be an external string.
1198 setNonInlineChars<char16_t>(s);
1199 d.u1.flags = INIT_FLAT_FLAGS;
1200
1201 return &this->asFlat();
1202 }
1203
1204 #ifdef DEBUG
dump(js::GenericPrinter & out)1205 void JSAtom::dump(js::GenericPrinter& out) {
1206 out.printf("JSAtom* (%p) = ", (void*)this);
1207 this->JSString::dump(out);
1208 }
1209
dump()1210 void JSAtom::dump() {
1211 Fprinter out(stderr);
1212 dump(out);
1213 }
1214
dumpRepresentation(js::GenericPrinter & out,int indent) const1215 void JSExternalString::dumpRepresentation(js::GenericPrinter& out,
1216 int indent) const {
1217 dumpRepresentationHeader(out, "JSExternalString");
1218 indent += 2;
1219
1220 out.printf("%*sfinalizer: ((JSStringFinalizer*) %p)\n", indent, "",
1221 externalFinalizer());
1222 dumpRepresentationChars(out, indent);
1223 }
1224 #endif /* DEBUG */
1225
NewDependentString(JSContext * cx,JSString * baseArg,size_t start,size_t length)1226 JSLinearString* js::NewDependentString(JSContext* cx, JSString* baseArg,
1227 size_t start, size_t length) {
1228 if (length == 0) return cx->emptyString();
1229
1230 JSLinearString* base = baseArg->ensureLinear(cx);
1231 if (!base) return nullptr;
1232
1233 if (start == 0 && length == base->length()) return base;
1234
1235 if (base->hasTwoByteChars()) {
1236 AutoCheckCannotGC nogc;
1237 const char16_t* chars = base->twoByteChars(nogc) + start;
1238 if (JSLinearString* staticStr = cx->staticStrings().lookup(chars, length))
1239 return staticStr;
1240 } else {
1241 AutoCheckCannotGC nogc;
1242 const Latin1Char* chars = base->latin1Chars(nogc) + start;
1243 if (JSLinearString* staticStr = cx->staticStrings().lookup(chars, length))
1244 return staticStr;
1245 }
1246
1247 return JSDependentString::new_(cx, base, start, length);
1248 }
1249
CanStoreCharsAsLatin1(const char16_t * s,size_t length)1250 static bool CanStoreCharsAsLatin1(const char16_t* s, size_t length) {
1251 for (const char16_t* end = s + length; s < end; ++s) {
1252 if (*s > JSString::MAX_LATIN1_CHAR) return false;
1253 }
1254
1255 return true;
1256 }
1257
CanStoreCharsAsLatin1(const Latin1Char * s,size_t length)1258 static bool CanStoreCharsAsLatin1(const Latin1Char* s, size_t length) {
1259 MOZ_CRASH("Shouldn't be called for Latin1 chars");
1260 }
1261
1262 template <AllowGC allowGC>
NewInlineStringDeflated(JSContext * cx,mozilla::Range<const char16_t> chars)1263 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringDeflated(
1264 JSContext* cx, mozilla::Range<const char16_t> chars) {
1265 size_t len = chars.length();
1266 Latin1Char* storage;
1267 JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage);
1268 if (!str) return nullptr;
1269
1270 for (size_t i = 0; i < len; i++) {
1271 MOZ_ASSERT(chars[i] <= JSString::MAX_LATIN1_CHAR);
1272 storage[i] = Latin1Char(chars[i]);
1273 }
1274 storage[len] = '\0';
1275 return str;
1276 }
1277
1278 template <typename CharT>
TryEmptyOrStaticString(JSContext * cx,const CharT * chars,size_t n)1279 static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString(
1280 JSContext* cx, const CharT* chars, size_t n) {
1281 // Measurements on popular websites indicate empty strings are pretty common
1282 // and most strings with length 1 or 2 are in the StaticStrings table. For
1283 // length 3 strings that's only about 1%, so we check n <= 2.
1284 if (n <= 2) {
1285 if (n == 0) return cx->emptyString();
1286
1287 if (JSFlatString* str = cx->staticStrings().lookup(chars, n)) return str;
1288 }
1289
1290 return nullptr;
1291 }
1292
1293 template <AllowGC allowGC>
NewStringDeflated(JSContext * cx,const char16_t * s,size_t n)1294 static JSFlatString* NewStringDeflated(JSContext* cx, const char16_t* s,
1295 size_t n) {
1296 if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) return str;
1297
1298 if (JSInlineString::lengthFits<Latin1Char>(n))
1299 return NewInlineStringDeflated<allowGC>(
1300 cx, mozilla::Range<const char16_t>(s, n));
1301
1302 ScopedJSFreePtr<Latin1Char> news(cx->pod_malloc<Latin1Char>(n + 1));
1303 if (!news) return nullptr;
1304
1305 for (size_t i = 0; i < n; i++) {
1306 MOZ_ASSERT(s[i] <= JSString::MAX_LATIN1_CHAR);
1307 news.get()[i] = Latin1Char(s[i]);
1308 }
1309 news[n] = '\0';
1310
1311 JSFlatString* str = JSFlatString::new_<allowGC>(cx, news.get(), n);
1312 if (!str) return nullptr;
1313
1314 news.forget();
1315 return str;
1316 }
1317
1318 template <AllowGC allowGC>
NewStringDeflated(JSContext * cx,const Latin1Char * s,size_t n)1319 static JSFlatString* NewStringDeflated(JSContext* cx, const Latin1Char* s,
1320 size_t n) {
1321 MOZ_CRASH("Shouldn't be called for Latin1 chars");
1322 }
1323
1324 template <AllowGC allowGC, typename CharT>
NewStringDontDeflate(JSContext * cx,CharT * chars,size_t length)1325 JSFlatString* js::NewStringDontDeflate(JSContext* cx, CharT* chars,
1326 size_t length) {
1327 if (JSFlatString* str = TryEmptyOrStaticString(cx, chars, length)) {
1328 // Free |chars| because we're taking possession of it, but it's no
1329 // longer needed because we use the static string instead.
1330 js_free(chars);
1331 return str;
1332 }
1333
1334 if (JSInlineString::lengthFits<CharT>(length)) {
1335 JSInlineString* str = NewInlineString<allowGC>(
1336 cx, mozilla::Range<const CharT>(chars, length));
1337 if (!str) return nullptr;
1338
1339 js_free(chars);
1340 return str;
1341 }
1342
1343 return JSFlatString::new_<allowGC>(cx, chars, length);
1344 }
1345
1346 template JSFlatString* js::NewStringDontDeflate<CanGC>(JSContext* cx,
1347 char16_t* chars,
1348 size_t length);
1349
1350 template JSFlatString* js::NewStringDontDeflate<NoGC>(JSContext* cx,
1351 char16_t* chars,
1352 size_t length);
1353
1354 template JSFlatString* js::NewStringDontDeflate<CanGC>(JSContext* cx,
1355 Latin1Char* chars,
1356 size_t length);
1357
1358 template JSFlatString* js::NewStringDontDeflate<NoGC>(JSContext* cx,
1359 Latin1Char* chars,
1360 size_t length);
1361
1362 template <AllowGC allowGC, typename CharT>
NewString(JSContext * cx,CharT * chars,size_t length)1363 JSFlatString* js::NewString(JSContext* cx, CharT* chars, size_t length) {
1364 if (IsSame<CharT, char16_t>::value && CanStoreCharsAsLatin1(chars, length)) {
1365 JSFlatString* s = NewStringDeflated<allowGC>(cx, chars, length);
1366 if (!s) return nullptr;
1367
1368 // Free |chars| because we're taking possession of it but not using it.
1369 js_free(chars);
1370 return s;
1371 }
1372
1373 return NewStringDontDeflate<allowGC>(cx, chars, length);
1374 }
1375
1376 template JSFlatString* js::NewString<CanGC>(JSContext* cx, char16_t* chars,
1377 size_t length);
1378
1379 template JSFlatString* js::NewString<NoGC>(JSContext* cx, char16_t* chars,
1380 size_t length);
1381
1382 template JSFlatString* js::NewString<CanGC>(JSContext* cx, Latin1Char* chars,
1383 size_t length);
1384
1385 template JSFlatString* js::NewString<NoGC>(JSContext* cx, Latin1Char* chars,
1386 size_t length);
1387
1388 namespace js {
1389
1390 template <AllowGC allowGC, typename CharT>
NewStringCopyNDontDeflate(JSContext * cx,const CharT * s,size_t n)1391 JSFlatString* NewStringCopyNDontDeflate(JSContext* cx, const CharT* s,
1392 size_t n) {
1393 if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) return str;
1394
1395 if (JSInlineString::lengthFits<CharT>(n))
1396 return NewInlineString<allowGC>(cx, mozilla::Range<const CharT>(s, n));
1397
1398 ScopedJSFreePtr<CharT> news(cx->pod_malloc<CharT>(n + 1));
1399 if (!news) {
1400 if (!allowGC) cx->recoverFromOutOfMemory();
1401 return nullptr;
1402 }
1403
1404 PodCopy(news.get(), s, n);
1405 news[n] = 0;
1406
1407 JSFlatString* str = JSFlatString::new_<allowGC>(cx, news.get(), n);
1408 if (!str) return nullptr;
1409
1410 news.forget();
1411 return str;
1412 }
1413
1414 template JSFlatString* NewStringCopyNDontDeflate<CanGC>(JSContext* cx,
1415 const char16_t* s,
1416 size_t n);
1417
1418 template JSFlatString* NewStringCopyNDontDeflate<NoGC>(JSContext* cx,
1419 const char16_t* s,
1420 size_t n);
1421
1422 template JSFlatString* NewStringCopyNDontDeflate<CanGC>(JSContext* cx,
1423 const Latin1Char* s,
1424 size_t n);
1425
1426 template JSFlatString* NewStringCopyNDontDeflate<NoGC>(JSContext* cx,
1427 const Latin1Char* s,
1428 size_t n);
1429
NewLatin1StringZ(JSContext * cx,UniqueChars chars)1430 JSFlatString* NewLatin1StringZ(JSContext* cx, UniqueChars chars) {
1431 JSFlatString* str =
1432 NewString<CanGC>(cx, (Latin1Char*)chars.get(), strlen(chars.get()));
1433 if (!str) return nullptr;
1434
1435 mozilla::Unused << chars.release();
1436 return str;
1437 }
1438
1439 template <AllowGC allowGC, typename CharT>
NewStringCopyN(JSContext * cx,const CharT * s,size_t n)1440 JSFlatString* NewStringCopyN(JSContext* cx, const CharT* s, size_t n) {
1441 if (IsSame<CharT, char16_t>::value && CanStoreCharsAsLatin1(s, n))
1442 return NewStringDeflated<allowGC>(cx, s, n);
1443
1444 return NewStringCopyNDontDeflate<allowGC>(cx, s, n);
1445 }
1446
1447 template JSFlatString* NewStringCopyN<CanGC>(JSContext* cx, const char16_t* s,
1448 size_t n);
1449
1450 template JSFlatString* NewStringCopyN<NoGC>(JSContext* cx, const char16_t* s,
1451 size_t n);
1452
1453 template JSFlatString* NewStringCopyN<CanGC>(JSContext* cx, const Latin1Char* s,
1454 size_t n);
1455
1456 template JSFlatString* NewStringCopyN<NoGC>(JSContext* cx, const Latin1Char* s,
1457 size_t n);
1458
1459 template <js::AllowGC allowGC>
NewStringCopyUTF8N(JSContext * cx,const JS::UTF8Chars utf8)1460 JSFlatString* NewStringCopyUTF8N(JSContext* cx, const JS::UTF8Chars utf8) {
1461 JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8);
1462 if (encoding == JS::SmallestEncoding::ASCII)
1463 return NewStringCopyN<allowGC>(cx, utf8.begin().get(), utf8.length());
1464
1465 size_t length;
1466 if (encoding == JS::SmallestEncoding::Latin1) {
1467 Latin1Char* latin1 = UTF8CharsToNewLatin1CharsZ(cx, utf8, &length).get();
1468 if (!latin1) return nullptr;
1469
1470 JSFlatString* result = NewString<allowGC>(cx, latin1, length);
1471 if (!result) js_free((void*)latin1);
1472 return result;
1473 }
1474
1475 MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16);
1476
1477 char16_t* utf16 = UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length).get();
1478 if (!utf16) return nullptr;
1479
1480 JSFlatString* result = NewString<allowGC>(cx, utf16, length);
1481 if (!result) js_free((void*)utf16);
1482 return result;
1483 }
1484
1485 template JSFlatString* NewStringCopyUTF8N<CanGC>(JSContext* cx,
1486 const JS::UTF8Chars utf8);
1487
lookup(const char16_t * chars,size_t len) const1488 MOZ_ALWAYS_INLINE JSString* ExternalStringCache::lookup(const char16_t* chars,
1489 size_t len) const {
1490 AutoCheckCannotGC nogc;
1491
1492 for (size_t i = 0; i < NumEntries; i++) {
1493 JSString* str = entries_[i];
1494 if (!str || str->length() != len) continue;
1495
1496 const char16_t* strChars = str->asLinear().nonInlineTwoByteChars(nogc);
1497 if (chars == strChars) {
1498 // Note that we don't need an incremental barrier here or below.
1499 // The cache is purged on GC so any string we get from the cache
1500 // must have been allocated after the GC started.
1501 return str;
1502 }
1503
1504 // Compare the chars. Don't do this for long strings as it will be
1505 // faster to allocate a new external string.
1506 static const size_t MaxLengthForCharComparison = 100;
1507 if (len <= MaxLengthForCharComparison && PodEqual(chars, strChars, len))
1508 return str;
1509 }
1510
1511 return nullptr;
1512 }
1513
put(JSString * str)1514 MOZ_ALWAYS_INLINE void ExternalStringCache::put(JSString* str) {
1515 MOZ_ASSERT(str->isExternal());
1516
1517 for (size_t i = NumEntries - 1; i > 0; i--) entries_[i] = entries_[i - 1];
1518
1519 entries_[0] = str;
1520 }
1521
NewMaybeExternalString(JSContext * cx,const char16_t * s,size_t n,const JSStringFinalizer * fin,bool * allocatedExternal)1522 JSString* NewMaybeExternalString(JSContext* cx, const char16_t* s, size_t n,
1523 const JSStringFinalizer* fin,
1524 bool* allocatedExternal) {
1525 if (JSString* str = TryEmptyOrStaticString(cx, s, n)) {
1526 *allocatedExternal = false;
1527 return str;
1528 }
1529
1530 if (JSThinInlineString::lengthFits<Latin1Char>(n) &&
1531 CanStoreCharsAsLatin1(s, n)) {
1532 *allocatedExternal = false;
1533 return NewInlineStringDeflated<AllowGC::CanGC>(
1534 cx, mozilla::Range<const char16_t>(s, n));
1535 }
1536
1537 ExternalStringCache& cache = cx->zone()->externalStringCache();
1538 if (JSString* str = cache.lookup(s, n)) {
1539 *allocatedExternal = false;
1540 return str;
1541 }
1542
1543 JSString* str = JSExternalString::new_(cx, s, n, fin);
1544 if (!str) return nullptr;
1545
1546 *allocatedExternal = true;
1547 cache.put(str);
1548 return str;
1549 }
1550
1551 } /* namespace js */
1552
1553 #ifdef DEBUG
dumpRepresentation(js::GenericPrinter & out,int indent) const1554 void JSExtensibleString::dumpRepresentation(js::GenericPrinter& out,
1555 int indent) const {
1556 dumpRepresentationHeader(out, "JSExtensibleString");
1557 indent += 2;
1558
1559 out.printf("%*scapacity: %zu\n", indent, "", capacity());
1560 dumpRepresentationChars(out, indent);
1561 }
1562
dumpRepresentation(js::GenericPrinter & out,int indent) const1563 void JSInlineString::dumpRepresentation(js::GenericPrinter& out,
1564 int indent) const {
1565 dumpRepresentationHeader(
1566 out, isFatInline() ? "JSFatInlineString" : "JSThinInlineString");
1567 indent += 2;
1568
1569 dumpRepresentationChars(out, indent);
1570 }
1571
dumpRepresentation(js::GenericPrinter & out,int indent) const1572 void JSFlatString::dumpRepresentation(js::GenericPrinter& out,
1573 int indent) const {
1574 dumpRepresentationHeader(out, "JSFlatString");
1575 indent += 2;
1576
1577 dumpRepresentationChars(out, indent);
1578 }
1579 #endif
1580
1581 static void FinalizeRepresentativeExternalString(const JSStringFinalizer* fin,
1582 char16_t* chars);
1583
1584 static const JSStringFinalizer RepresentativeExternalStringFinalizer = {
1585 FinalizeRepresentativeExternalString};
1586
FinalizeRepresentativeExternalString(const JSStringFinalizer * fin,char16_t * chars)1587 static void FinalizeRepresentativeExternalString(const JSStringFinalizer* fin,
1588 char16_t* chars) {
1589 // Constant chars, nothing to free.
1590 MOZ_ASSERT(fin == &RepresentativeExternalStringFinalizer);
1591 }
1592
1593 template <typename CheckString, typename CharT>
FillWithRepresentatives(JSContext * cx,HandleArrayObject array,uint32_t * index,const CharT * chars,size_t len,size_t fatInlineMaxLength,const CheckString & check)1594 static bool FillWithRepresentatives(JSContext* cx, HandleArrayObject array,
1595 uint32_t* index, const CharT* chars,
1596 size_t len, size_t fatInlineMaxLength,
1597 const CheckString& check) {
1598 auto AppendString = [&check](JSContext* cx, HandleArrayObject array,
1599 uint32_t* index, HandleString s) {
1600 MOZ_ASSERT(check(s));
1601 Unused << check; // silence clang -Wunused-lambda-capture in opt builds
1602 RootedValue val(cx, StringValue(s));
1603 return JS_DefineElement(cx, array, (*index)++, val, 0);
1604 };
1605
1606 MOZ_ASSERT(len > fatInlineMaxLength);
1607
1608 // Normal atom.
1609 RootedString atom1(cx, AtomizeChars(cx, chars, len));
1610 if (!atom1 || !AppendString(cx, array, index, atom1)) return false;
1611 MOZ_ASSERT(atom1->isAtom());
1612
1613 // Inline atom.
1614 RootedString atom2(cx, AtomizeChars(cx, chars, 2));
1615 if (!atom2 || !AppendString(cx, array, index, atom2)) return false;
1616 MOZ_ASSERT(atom2->isAtom());
1617 MOZ_ASSERT(atom2->isInline());
1618
1619 // Fat inline atom.
1620 RootedString atom3(cx, AtomizeChars(cx, chars, fatInlineMaxLength));
1621 if (!atom3 || !AppendString(cx, array, index, atom3)) return false;
1622 MOZ_ASSERT(atom3->isAtom());
1623 MOZ_ASSERT(atom3->isFatInline());
1624
1625 // Normal flat string.
1626 RootedString flat1(cx, NewStringCopyN<CanGC>(cx, chars, len));
1627 if (!flat1 || !AppendString(cx, array, index, flat1)) return false;
1628 MOZ_ASSERT(flat1->isFlat());
1629
1630 // Inline string.
1631 RootedString flat2(cx, NewStringCopyN<CanGC>(cx, chars, 3));
1632 if (!flat2 || !AppendString(cx, array, index, flat2)) return false;
1633 MOZ_ASSERT(flat2->isFlat());
1634 MOZ_ASSERT(flat2->isInline());
1635
1636 // Fat inline string.
1637 RootedString flat3(cx, NewStringCopyN<CanGC>(cx, chars, fatInlineMaxLength));
1638 if (!flat3 || !AppendString(cx, array, index, flat3)) return false;
1639 MOZ_ASSERT(flat3->isFlat());
1640 MOZ_ASSERT(flat3->isFatInline());
1641
1642 // Rope.
1643 RootedString rope(cx, ConcatStrings<CanGC>(cx, atom1, atom3));
1644 if (!rope || !AppendString(cx, array, index, rope)) return false;
1645 MOZ_ASSERT(rope->isRope());
1646
1647 // Dependent.
1648 RootedString dep(cx, NewDependentString(cx, atom1, 0, len - 2));
1649 if (!dep || !AppendString(cx, array, index, dep)) return false;
1650 MOZ_ASSERT(dep->isDependent());
1651
1652 // Undepended.
1653 RootedString undep(cx, NewDependentString(cx, atom1, 0, len - 3));
1654 if (!undep || !undep->ensureFlat(cx) ||
1655 !AppendString(cx, array, index, undep))
1656 return false;
1657 MOZ_ASSERT(undep->isUndepended());
1658
1659 // Extensible.
1660 RootedString temp1(cx, NewStringCopyN<CanGC>(cx, chars, len));
1661 if (!temp1) return false;
1662 RootedString extensible(cx, ConcatStrings<CanGC>(cx, temp1, atom3));
1663 if (!extensible || !extensible->ensureLinear(cx)) return false;
1664 if (!AppendString(cx, array, index, extensible)) return false;
1665 MOZ_ASSERT(extensible->isExtensible());
1666
1667 // External. Note that we currently only support TwoByte external strings.
1668 RootedString external1(cx), external2(cx);
1669 if (IsSame<CharT, char16_t>::value) {
1670 external1 = JS_NewExternalString(cx, (const char16_t*)chars, len,
1671 &RepresentativeExternalStringFinalizer);
1672 if (!external1 || !AppendString(cx, array, index, external1)) return false;
1673 MOZ_ASSERT(external1->isExternal());
1674
1675 external2 = JS_NewExternalString(cx, (const char16_t*)chars, 2,
1676 &RepresentativeExternalStringFinalizer);
1677 if (!external2 || !AppendString(cx, array, index, external2)) return false;
1678 MOZ_ASSERT(external2->isExternal());
1679 }
1680
1681 // Assert the strings still have the types we expect after creating the
1682 // other strings.
1683
1684 MOZ_ASSERT(atom1->isAtom());
1685 MOZ_ASSERT(atom2->isAtom());
1686 MOZ_ASSERT(atom3->isAtom());
1687 MOZ_ASSERT(atom2->isInline());
1688 MOZ_ASSERT(atom3->isFatInline());
1689
1690 MOZ_ASSERT(flat1->isFlat());
1691 MOZ_ASSERT(flat2->isFlat());
1692 MOZ_ASSERT(flat3->isFlat());
1693 MOZ_ASSERT(flat2->isInline());
1694 MOZ_ASSERT(flat3->isFatInline());
1695
1696 MOZ_ASSERT(rope->isRope());
1697 MOZ_ASSERT(dep->isDependent());
1698 MOZ_ASSERT(undep->isUndepended());
1699 MOZ_ASSERT(extensible->isExtensible());
1700 MOZ_ASSERT_IF(external1, external1->isExternal());
1701 MOZ_ASSERT_IF(external2, external2->isExternal());
1702 return true;
1703 }
1704
fillWithRepresentatives(JSContext * cx,HandleArrayObject array)1705 /* static */ bool JSString::fillWithRepresentatives(JSContext* cx,
1706 HandleArrayObject array) {
1707 uint32_t index = 0;
1708
1709 auto CheckTwoByte = [](JSString* str) { return str->hasTwoByteChars(); };
1710 auto CheckLatin1 = [](JSString* str) { return str->hasLatin1Chars(); };
1711
1712 // Append TwoByte strings.
1713 static const char16_t twoByteChars[] =
1714 u"\u1234abc\0def\u5678ghijklmasdfa\0xyz0123456789";
1715 if (!FillWithRepresentatives(cx, array, &index, twoByteChars,
1716 mozilla::ArrayLength(twoByteChars) - 1,
1717 JSFatInlineString::MAX_LENGTH_TWO_BYTE,
1718 CheckTwoByte)) {
1719 return false;
1720 }
1721
1722 // Append Latin1 strings.
1723 static const Latin1Char latin1Chars[] = "abc\0defghijklmasdfa\0xyz0123456789";
1724 if (!FillWithRepresentatives(
1725 cx, array, &index, latin1Chars, mozilla::ArrayLength(latin1Chars) - 1,
1726 JSFatInlineString::MAX_LENGTH_LATIN1, CheckLatin1)) {
1727 return false;
1728 }
1729
1730 MOZ_ASSERT(index == 22);
1731 return true;
1732 }
1733
1734 /*** Conversions ************************************************************/
1735
ValueToPrintable(JSContext * cx,const Value & vArg,JSAutoByteString * bytes,bool asSource)1736 const char* js::ValueToPrintable(JSContext* cx, const Value& vArg,
1737 JSAutoByteString* bytes, bool asSource) {
1738 RootedValue v(cx, vArg);
1739 JSString* str;
1740 if (asSource)
1741 str = ValueToSource(cx, v);
1742 else
1743 str = ToString<CanGC>(cx, v);
1744 if (!str) return nullptr;
1745 str = QuoteString(cx, str, 0);
1746 if (!str) return nullptr;
1747 return bytes->encodeLatin1(cx, str);
1748 }
1749
1750 template <AllowGC allowGC>
ToStringSlow(JSContext * cx,typename MaybeRooted<Value,allowGC>::HandleType arg)1751 JSString* js::ToStringSlow(
1752 JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg) {
1753 /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
1754 MOZ_ASSERT(!arg.isString());
1755
1756 Value v = arg;
1757 if (!v.isPrimitive()) {
1758 MOZ_ASSERT(!cx->helperThread());
1759 if (!allowGC) return nullptr;
1760 RootedValue v2(cx, v);
1761 if (!ToPrimitive(cx, JSTYPE_STRING, &v2)) return nullptr;
1762 v = v2;
1763 }
1764
1765 JSString* str;
1766 if (v.isString()) {
1767 str = v.toString();
1768 } else if (v.isInt32()) {
1769 str = Int32ToString<allowGC>(cx, v.toInt32());
1770 } else if (v.isDouble()) {
1771 str = NumberToString<allowGC>(cx, v.toDouble());
1772 } else if (v.isBoolean()) {
1773 str = BooleanToString(cx, v.toBoolean());
1774 } else if (v.isNull()) {
1775 str = cx->names().null;
1776 } else if (v.isSymbol()) {
1777 MOZ_ASSERT(!cx->helperThread());
1778 if (allowGC) {
1779 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1780 JSMSG_SYMBOL_TO_STRING);
1781 }
1782 return nullptr;
1783 } else {
1784 MOZ_ASSERT(v.isUndefined());
1785 str = cx->names().undefined;
1786 }
1787 return str;
1788 }
1789
1790 template JSString* js::ToStringSlow<CanGC>(JSContext* cx, HandleValue arg);
1791
1792 template JSString* js::ToStringSlow<NoGC>(JSContext* cx, const Value& arg);
1793
ToStringSlow(JSContext * cx,HandleValue v)1794 JS_PUBLIC_API JSString* js::ToStringSlow(JSContext* cx, HandleValue v) {
1795 return ToStringSlow<CanGC>(cx, v);
1796 }
1797
SymbolToSource(JSContext * cx,Symbol * symbol)1798 static JSString* SymbolToSource(JSContext* cx, Symbol* symbol) {
1799 RootedString desc(cx, symbol->description());
1800 SymbolCode code = symbol->code();
1801 if (code != SymbolCode::InSymbolRegistry &&
1802 code != SymbolCode::UniqueSymbol) {
1803 // Well-known symbol.
1804 MOZ_ASSERT(uint32_t(code) < JS::WellKnownSymbolLimit);
1805 return desc;
1806 }
1807
1808 StringBuffer buf(cx);
1809 if (code == SymbolCode::InSymbolRegistry ? !buf.append("Symbol.for(")
1810 : !buf.append("Symbol("))
1811 return nullptr;
1812 if (desc) {
1813 desc = StringToSource(cx, desc);
1814 if (!desc || !buf.append(desc)) return nullptr;
1815 }
1816 if (!buf.append(')')) return nullptr;
1817 return buf.finishString();
1818 }
1819
ValueToSource(JSContext * cx,HandleValue v)1820 JSString* js::ValueToSource(JSContext* cx, HandleValue v) {
1821 if (!CheckRecursionLimit(cx)) return nullptr;
1822 assertSameCompartment(cx, v);
1823
1824 if (v.isUndefined()) return cx->names().void0;
1825 if (v.isString()) return StringToSource(cx, v.toString());
1826 if (v.isSymbol()) return SymbolToSource(cx, v.toSymbol());
1827 if (v.isPrimitive()) {
1828 /* Special case to preserve negative zero, _contra_ toString. */
1829 if (v.isDouble() && IsNegativeZero(v.toDouble())) {
1830 static const Latin1Char negativeZero[] = {'-', '0'};
1831
1832 return NewStringCopyN<CanGC>(cx, negativeZero,
1833 mozilla::ArrayLength(negativeZero));
1834 }
1835 return ToString<CanGC>(cx, v);
1836 }
1837
1838 RootedValue fval(cx);
1839 RootedObject obj(cx, &v.toObject());
1840 if (!GetProperty(cx, obj, obj, cx->names().toSource, &fval)) return nullptr;
1841 if (IsCallable(fval)) {
1842 RootedValue v(cx);
1843 if (!js::Call(cx, fval, obj, &v)) return nullptr;
1844
1845 return ToString<CanGC>(cx, v);
1846 }
1847
1848 return ObjectToSource(cx, obj);
1849 }
1850
StringToSource(JSContext * cx,JSString * str)1851 JSString* js::StringToSource(JSContext* cx, JSString* str) {
1852 return QuoteString(cx, str, '"');
1853 }
1854