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