LCOV - code coverage report
Current view: top level - js/src - jsstr.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 793 2156 36.8 %
Date: 2017-07-14 16:53:18 Functions: 100 214 46.7 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "jsstr.h"
       8             : 
       9             : #include "mozilla/Attributes.h"
      10             : #include "mozilla/Casting.h"
      11             : #include "mozilla/CheckedInt.h"
      12             : #include "mozilla/FloatingPoint.h"
      13             : #include "mozilla/PodOperations.h"
      14             : #include "mozilla/Range.h"
      15             : #include "mozilla/TypeTraits.h"
      16             : #include "mozilla/Unused.h"
      17             : 
      18             : #include <ctype.h>
      19             : #include <limits>
      20             : #include <string.h>
      21             : 
      22             : #include "jsapi.h"
      23             : #include "jsarray.h"
      24             : #include "jsatom.h"
      25             : #include "jsbool.h"
      26             : #include "jscntxt.h"
      27             : #include "jsgc.h"
      28             : #include "jsnum.h"
      29             : #include "jsobj.h"
      30             : #include "jsopcode.h"
      31             : #include "jstypes.h"
      32             : #include "jsutil.h"
      33             : 
      34             : #include "builtin/RegExp.h"
      35             : #include "jit/InlinableNatives.h"
      36             : #include "js/Conversions.h"
      37             : #include "js/UniquePtr.h"
      38             : #if ENABLE_INTL_API
      39             : #include "unicode/uchar.h"
      40             : #include "unicode/unorm2.h"
      41             : #endif
      42             : #include "vm/GlobalObject.h"
      43             : #include "vm/Interpreter.h"
      44             : #include "vm/Opcodes.h"
      45             : #include "vm/Printer.h"
      46             : #include "vm/RegExpObject.h"
      47             : #include "vm/RegExpStatics.h"
      48             : #include "vm/StringBuffer.h"
      49             : #include "vm/Unicode.h"
      50             : 
      51             : #include "vm/Interpreter-inl.h"
      52             : #include "vm/String-inl.h"
      53             : #include "vm/StringObject-inl.h"
      54             : #include "vm/TypeInference-inl.h"
      55             : 
      56             : using namespace js;
      57             : using namespace js::gc;
      58             : 
      59             : using JS::Symbol;
      60             : using JS::SymbolCode;
      61             : using JS::ToInt32;
      62             : using JS::ToUint32;
      63             : 
      64             : using mozilla::AssertedCast;
      65             : using mozilla::CheckedInt;
      66             : using mozilla::IsNaN;
      67             : using mozilla::IsNegativeZero;
      68             : using mozilla::IsSame;
      69             : using mozilla::Move;
      70             : using mozilla::PodCopy;
      71             : using mozilla::PodEqual;
      72             : using mozilla::RangedPtr;
      73             : 
      74             : using JS::AutoCheckCannotGC;
      75             : 
      76             : static JSLinearString*
      77        1374 : ArgToRootedString(JSContext* cx, const CallArgs& args, unsigned argno)
      78             : {
      79        1374 :     if (argno >= args.length())
      80           0 :         return cx->names().undefined;
      81             : 
      82        1374 :     JSString* str = ToString<CanGC>(cx, args[argno]);
      83        1374 :     if (!str)
      84           0 :         return nullptr;
      85             : 
      86        1374 :     args[argno].setString(str);
      87        1374 :     return str->ensureLinear(cx);
      88             : }
      89             : 
      90             : /*
      91             :  * Forward declarations for URI encode/decode and helper routines
      92             :  */
      93             : static bool
      94             : str_decodeURI(JSContext* cx, unsigned argc, Value* vp);
      95             : 
      96             : static bool
      97             : str_decodeURI_Component(JSContext* cx, unsigned argc, Value* vp);
      98             : 
      99             : static bool
     100             : str_encodeURI(JSContext* cx, unsigned argc, Value* vp);
     101             : 
     102             : static bool
     103             : str_encodeURI_Component(JSContext* cx, unsigned argc, Value* vp);
     104             : 
     105             : /*
     106             :  * Global string methods
     107             :  */
     108             : 
     109             : 
     110             : /* ES5 B.2.1 */
     111             : template <typename CharT>
     112             : static Latin1Char*
     113           0 : Escape(JSContext* cx, const CharT* chars, uint32_t length, uint32_t* newLengthOut)
     114             : {
     115             :     static const uint8_t shouldPassThrough[128] = {
     116             :          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     117             :          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
     118             :          0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,       /*    !"#$%&'()*+,-./  */
     119             :          1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,       /*   0123456789:;<=>?  */
     120             :          1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,       /*   @ABCDEFGHIJKLMNO  */
     121             :          1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,       /*   PQRSTUVWXYZ[\]^_  */
     122             :          0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,       /*   `abcdefghijklmno  */
     123             :          1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,       /*   pqrstuvwxyz{\}~  DEL */
     124             :     };
     125             : 
     126             :     /* Take a first pass and see how big the result string will need to be. */
     127           0 :     uint32_t newLength = length;
     128           0 :     for (size_t i = 0; i < length; i++) {
     129           0 :         char16_t ch = chars[i];
     130           0 :         if (ch < 128 && shouldPassThrough[ch])
     131           0 :             continue;
     132             : 
     133             :         /* The character will be encoded as %XX or %uXXXX. */
     134           0 :         newLength += (ch < 256) ? 2 : 5;
     135             : 
     136             :         /*
     137             :          * newlength is incremented by at most 5 on each iteration, so worst
     138             :          * case newlength == length * 6. This can't overflow.
     139             :          */
     140             :         static_assert(JSString::MAX_LENGTH < UINT32_MAX / 6,
     141             :                       "newlength must not overflow");
     142             :     }
     143             : 
     144           0 :     Latin1Char* newChars = cx->pod_malloc<Latin1Char>(newLength + 1);
     145           0 :     if (!newChars)
     146           0 :         return nullptr;
     147             : 
     148             :     static const char digits[] = "0123456789ABCDEF";
     149             : 
     150             :     size_t i, ni;
     151           0 :     for (i = 0, ni = 0; i < length; i++) {
     152           0 :         char16_t ch = chars[i];
     153           0 :         if (ch < 128 && shouldPassThrough[ch]) {
     154           0 :             newChars[ni++] = ch;
     155           0 :         } else if (ch < 256) {
     156           0 :             newChars[ni++] = '%';
     157           0 :             newChars[ni++] = digits[ch >> 4];
     158           0 :             newChars[ni++] = digits[ch & 0xF];
     159             :         } else {
     160           0 :             newChars[ni++] = '%';
     161           0 :             newChars[ni++] = 'u';
     162           0 :             newChars[ni++] = digits[ch >> 12];
     163           0 :             newChars[ni++] = digits[(ch & 0xF00) >> 8];
     164           0 :             newChars[ni++] = digits[(ch & 0xF0) >> 4];
     165           0 :             newChars[ni++] = digits[ch & 0xF];
     166             :         }
     167             :     }
     168           0 :     MOZ_ASSERT(ni == newLength);
     169           0 :     newChars[newLength] = 0;
     170             : 
     171           0 :     *newLengthOut = newLength;
     172           0 :     return newChars;
     173             : }
     174             : 
     175             : static bool
     176           0 : str_escape(JSContext* cx, unsigned argc, Value* vp)
     177             : {
     178           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     179             : 
     180           0 :     JSLinearString* str = ArgToRootedString(cx, args, 0);
     181           0 :     if (!str)
     182           0 :         return false;
     183             : 
     184           0 :     ScopedJSFreePtr<Latin1Char> newChars;
     185           0 :     uint32_t newLength = 0;  // initialize to silence GCC warning
     186           0 :     if (str->hasLatin1Chars()) {
     187           0 :         AutoCheckCannotGC nogc;
     188           0 :         newChars = Escape(cx, str->latin1Chars(nogc), str->length(), &newLength);
     189             :     } else {
     190           0 :         AutoCheckCannotGC nogc;
     191           0 :         newChars = Escape(cx, str->twoByteChars(nogc), str->length(), &newLength);
     192             :     }
     193             : 
     194           0 :     if (!newChars)
     195           0 :         return false;
     196             : 
     197           0 :     JSString* res = NewString<CanGC>(cx, newChars.get(), newLength);
     198           0 :     if (!res)
     199           0 :         return false;
     200             : 
     201           0 :     newChars.forget();
     202           0 :     args.rval().setString(res);
     203           0 :     return true;
     204             : }
     205             : 
     206             : template <typename CharT>
     207             : static inline bool
     208           0 : Unhex4(const RangedPtr<const CharT> chars, char16_t* result)
     209             : {
     210           0 :     char16_t a = chars[0],
     211           0 :              b = chars[1],
     212           0 :              c = chars[2],
     213           0 :              d = chars[3];
     214             : 
     215           0 :     if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d)))
     216           0 :         return false;
     217             : 
     218           0 :     *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d);
     219           0 :     return true;
     220             : }
     221             : 
     222             : template <typename CharT>
     223             : static inline bool
     224           0 : Unhex2(const RangedPtr<const CharT> chars, char16_t* result)
     225             : {
     226           0 :     char16_t a = chars[0],
     227           0 :              b = chars[1];
     228             : 
     229           0 :     if (!(JS7_ISHEX(a) && JS7_ISHEX(b)))
     230           0 :         return false;
     231             : 
     232           0 :     *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b);
     233           0 :     return true;
     234             : }
     235             : 
     236             : template <typename CharT>
     237             : static bool
     238           0 : Unescape(StringBuffer& sb, const mozilla::Range<const CharT> chars)
     239             : {
     240             :     /*
     241             :      * NB: use signed integers for length/index to allow simple length
     242             :      * comparisons without unsigned-underflow hazards.
     243             :      */
     244             :     static_assert(JSString::MAX_LENGTH <= INT_MAX, "String length must fit in a signed integer");
     245           0 :     int length = AssertedCast<int>(chars.length());
     246             : 
     247             :     /*
     248             :      * Note that the spec algorithm has been optimized to avoid building
     249             :      * a string in the case where no escapes are present.
     250             :      */
     251             : 
     252             :     /* Step 4. */
     253           0 :     int k = 0;
     254           0 :     bool building = false;
     255             : 
     256             :     /* Step 5. */
     257           0 :     while (k < length) {
     258             :         /* Step 6. */
     259           0 :         char16_t c = chars[k];
     260             : 
     261             :         /* Step 7. */
     262           0 :         if (c != '%')
     263           0 :             goto step_18;
     264             : 
     265             :         /* Step 8. */
     266           0 :         if (k > length - 6)
     267           0 :             goto step_14;
     268             : 
     269             :         /* Step 9. */
     270           0 :         if (chars[k + 1] != 'u')
     271           0 :             goto step_14;
     272             : 
     273             : #define ENSURE_BUILDING                                      \
     274             :         do {                                                 \
     275             :             if (!building) {                                 \
     276             :                 building = true;                             \
     277             :                 if (!sb.reserve(length))                     \
     278             :                     return false;                            \
     279             :                 sb.infallibleAppend(chars.begin().get(), k); \
     280             :             }                                                \
     281             :         } while(false);
     282             : 
     283             :         /* Step 10-13. */
     284           0 :         if (Unhex4(chars.begin() + k + 2, &c)) {
     285           0 :             ENSURE_BUILDING;
     286           0 :             k += 5;
     287           0 :             goto step_18;
     288             :         }
     289             : 
     290             :       step_14:
     291             :         /* Step 14. */
     292           0 :         if (k > length - 3)
     293           0 :             goto step_18;
     294             : 
     295             :         /* Step 15-17. */
     296           0 :         if (Unhex2(chars.begin() + k + 1, &c)) {
     297           0 :             ENSURE_BUILDING;
     298           0 :             k += 2;
     299             :         }
     300             : 
     301             :       step_18:
     302           0 :         if (building && !sb.append(c))
     303           0 :             return false;
     304             : 
     305             :         /* Step 19. */
     306           0 :         k += 1;
     307             :     }
     308             : 
     309           0 :     return true;
     310             : #undef ENSURE_BUILDING
     311             : }
     312             : 
     313             : /* ES5 B.2.2 */
     314             : static bool
     315           0 : str_unescape(JSContext* cx, unsigned argc, Value* vp)
     316             : {
     317           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     318             : 
     319             :     /* Step 1. */
     320           0 :     RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
     321           0 :     if (!str)
     322           0 :         return false;
     323             : 
     324             :     /* Step 3. */
     325           0 :     StringBuffer sb(cx);
     326           0 :     if (str->hasTwoByteChars() && !sb.ensureTwoByteChars())
     327           0 :         return false;
     328             : 
     329           0 :     if (str->hasLatin1Chars()) {
     330           0 :         AutoCheckCannotGC nogc;
     331           0 :         if (!Unescape(sb, str->latin1Range(nogc)))
     332           0 :             return false;
     333             :     } else {
     334           0 :         AutoCheckCannotGC nogc;
     335           0 :         if (!Unescape(sb, str->twoByteRange(nogc)))
     336           0 :             return false;
     337             :     }
     338             : 
     339             :     JSLinearString* result;
     340           0 :     if (!sb.empty()) {
     341           0 :         result = sb.finishString();
     342           0 :         if (!result)
     343           0 :             return false;
     344             :     } else {
     345           0 :         result = str;
     346             :     }
     347             : 
     348           0 :     args.rval().setString(result);
     349           0 :     return true;
     350             : }
     351             : 
     352             : #if JS_HAS_UNEVAL
     353             : static bool
     354           0 : str_uneval(JSContext* cx, unsigned argc, Value* vp)
     355             : {
     356           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     357           0 :     JSString* str = ValueToSource(cx, args.get(0));
     358           0 :     if (!str)
     359           0 :         return false;
     360             : 
     361           0 :     args.rval().setString(str);
     362           0 :     return true;
     363             : }
     364             : #endif
     365             : 
     366             : static const JSFunctionSpec string_functions[] = {
     367             :     JS_FN(js_escape_str,             str_escape,                1, JSPROP_RESOLVING),
     368             :     JS_FN(js_unescape_str,           str_unescape,              1, JSPROP_RESOLVING),
     369             : #if JS_HAS_UNEVAL
     370             :     JS_FN(js_uneval_str,             str_uneval,                1, JSPROP_RESOLVING),
     371             : #endif
     372             :     JS_FN(js_decodeURI_str,          str_decodeURI,             1, JSPROP_RESOLVING),
     373             :     JS_FN(js_encodeURI_str,          str_encodeURI,             1, JSPROP_RESOLVING),
     374             :     JS_FN(js_decodeURIComponent_str, str_decodeURI_Component,   1, JSPROP_RESOLVING),
     375             :     JS_FN(js_encodeURIComponent_str, str_encodeURI_Component,   1, JSPROP_RESOLVING),
     376             : 
     377             :     JS_FS_END
     378             : };
     379             : 
     380             : static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
     381             : 
     382             : static bool
     383           1 : str_enumerate(JSContext* cx, HandleObject obj)
     384             : {
     385           2 :     RootedString str(cx, obj->as<StringObject>().unbox());
     386           2 :     RootedValue value(cx);
     387           1 :     for (size_t i = 0, length = str->length(); i < length; i++) {
     388           0 :         JSString* str1 = NewDependentString(cx, str, i, 1);
     389           0 :         if (!str1)
     390           0 :             return false;
     391           0 :         value.setString(str1);
     392           0 :         if (!DefineElement(cx, obj, i, value, nullptr, nullptr,
     393             :                            STRING_ELEMENT_ATTRS | JSPROP_RESOLVING))
     394             :         {
     395           0 :             return false;
     396             :         }
     397             :     }
     398             : 
     399           1 :     return true;
     400             : }
     401             : 
     402             : static bool
     403          15 : str_mayResolve(const JSAtomState&, jsid id, JSObject*)
     404             : {
     405             :     // str_resolve ignores non-integer ids.
     406          15 :     return JSID_IS_INT(id);
     407             : }
     408             : 
     409             : static bool
     410        2291 : str_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
     411             : {
     412        2291 :     if (!JSID_IS_INT(id))
     413        2291 :         return true;
     414             : 
     415           0 :     RootedString str(cx, obj->as<StringObject>().unbox());
     416             : 
     417           0 :     int32_t slot = JSID_TO_INT(id);
     418           0 :     if ((size_t)slot < str->length()) {
     419           0 :         JSString* str1 = cx->staticStrings().getUnitStringForElement(cx, str, size_t(slot));
     420           0 :         if (!str1)
     421           0 :             return false;
     422           0 :         RootedValue value(cx, StringValue(str1));
     423           0 :         if (!DefineElement(cx, obj, uint32_t(slot), value, nullptr, nullptr,
     424             :                            STRING_ELEMENT_ATTRS | JSPROP_RESOLVING))
     425             :         {
     426           0 :             return false;
     427             :         }
     428           0 :         *resolvedp = true;
     429             :     }
     430           0 :     return true;
     431             : }
     432             : 
     433             : static const ClassOps StringObjectClassOps = {
     434             :     nullptr, /* addProperty */
     435             :     nullptr, /* delProperty */
     436             :     nullptr, /* getProperty */
     437             :     nullptr, /* setProperty */
     438             :     str_enumerate,
     439             :     nullptr, /* newEnumerate */
     440             :     str_resolve,
     441             :     str_mayResolve
     442             : };
     443             : 
     444             : const Class StringObject::class_ = {
     445             :     js_String_str,
     446             :     JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
     447             :     JSCLASS_HAS_CACHED_PROTO(JSProto_String),
     448             :     &StringObjectClassOps
     449             : };
     450             : 
     451             : /*
     452             :  * Perform the initial |RequireObjectCoercible(thisv)| and |ToString(thisv)|
     453             :  * from nearly all String.prototype.* functions.
     454             :  */
     455             : static MOZ_ALWAYS_INLINE JSString*
     456         954 : ToStringForStringFunction(JSContext* cx, HandleValue thisv)
     457             : {
     458         954 :     if (!CheckRecursionLimit(cx))
     459           0 :         return nullptr;
     460             : 
     461         954 :     if (thisv.isString())
     462         953 :         return thisv.toString();
     463             : 
     464           1 :     if (thisv.isObject()) {
     465           1 :         RootedObject obj(cx, &thisv.toObject());
     466           1 :         if (obj->is<StringObject>()) {
     467           1 :             StringObject* nobj = &obj->as<StringObject>();
     468             :             // We have to make sure that the ToPrimitive call from ToString
     469             :             // would be unobservable.
     470           2 :             if (HasNoToPrimitiveMethodPure(nobj, cx) &&
     471           1 :                 HasNativeMethodPure(nobj, cx->names().toString, str_toString, cx))
     472             :             {
     473           1 :                 return nobj->unbox();
     474             :             }
     475             :         }
     476           0 :     } else if (thisv.isNullOrUndefined()) {
     477           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
     478           0 :                                   thisv.isNull() ? "null" : "undefined", "object");
     479           0 :         return nullptr;
     480             :     }
     481             : 
     482           0 :     return ToStringSlow<CanGC>(cx, thisv);
     483             : }
     484             : 
     485             : MOZ_ALWAYS_INLINE bool
     486           0 : IsString(HandleValue v)
     487             : {
     488           0 :     return v.isString() || (v.isObject() && v.toObject().is<StringObject>());
     489             : }
     490             : 
     491             : #if JS_HAS_TOSOURCE
     492             : 
     493             : MOZ_ALWAYS_INLINE bool
     494           0 : str_toSource_impl(JSContext* cx, const CallArgs& args)
     495             : {
     496           0 :     MOZ_ASSERT(IsString(args.thisv()));
     497             : 
     498           0 :     Rooted<JSString*> str(cx, ToString<CanGC>(cx, args.thisv()));
     499           0 :     if (!str)
     500           0 :         return false;
     501             : 
     502           0 :     str = QuoteString(cx, str, '"');
     503           0 :     if (!str)
     504           0 :         return false;
     505             : 
     506           0 :     StringBuffer sb(cx);
     507           0 :     if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))"))
     508           0 :         return false;
     509             : 
     510           0 :     str = sb.finishString();
     511           0 :     if (!str)
     512           0 :         return false;
     513           0 :     args.rval().setString(str);
     514           0 :     return true;
     515             : }
     516             : 
     517             : static bool
     518           0 : str_toSource(JSContext* cx, unsigned argc, Value* vp)
     519             : {
     520           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     521           0 :     return CallNonGenericMethod<IsString, str_toSource_impl>(cx, args);
     522             : }
     523             : 
     524             : #endif /* JS_HAS_TOSOURCE */
     525             : 
     526             : MOZ_ALWAYS_INLINE bool
     527           0 : str_toString_impl(JSContext* cx, const CallArgs& args)
     528             : {
     529           0 :     MOZ_ASSERT(IsString(args.thisv()));
     530             : 
     531           0 :     args.rval().setString(args.thisv().isString()
     532           0 :                               ? args.thisv().toString()
     533           0 :                               : args.thisv().toObject().as<StringObject>().unbox());
     534           0 :     return true;
     535             : }
     536             : 
     537             : bool
     538           0 : js::str_toString(JSContext* cx, unsigned argc, Value* vp)
     539             : {
     540           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     541           0 :     return CallNonGenericMethod<IsString, str_toString_impl>(cx, args);
     542             : }
     543             : 
     544             : /*
     545             :  * Java-like string native methods.
     546             :  */
     547             : 
     548             : JSString*
     549         177 : js::SubstringKernel(JSContext* cx, HandleString str, int32_t beginInt, int32_t lengthInt)
     550             : {
     551         177 :     MOZ_ASSERT(0 <= beginInt);
     552         177 :     MOZ_ASSERT(0 <= lengthInt);
     553         177 :     MOZ_ASSERT(uint32_t(beginInt) <= str->length());
     554         177 :     MOZ_ASSERT(uint32_t(lengthInt) <= str->length() - beginInt);
     555             : 
     556         177 :     uint32_t begin = beginInt;
     557         177 :     uint32_t len = lengthInt;
     558             : 
     559             :     /*
     560             :      * Optimization for one level deep ropes.
     561             :      * This is common for the following pattern:
     562             :      *
     563             :      * while() {
     564             :      *   text = text.substr(0, x) + "bla" + text.substr(x)
     565             :      *   test.charCodeAt(x + 1)
     566             :      * }
     567             :      */
     568         177 :     if (str->isRope()) {
     569           0 :         JSRope* rope = &str->asRope();
     570             : 
     571             :         /* Substring is totally in leftChild of rope. */
     572           0 :         if (begin + len <= rope->leftChild()->length())
     573           0 :             return NewDependentString(cx, rope->leftChild(), begin, len);
     574             : 
     575             :         /* Substring is totally in rightChild of rope. */
     576           0 :         if (begin >= rope->leftChild()->length()) {
     577           0 :             begin -= rope->leftChild()->length();
     578           0 :             return NewDependentString(cx, rope->rightChild(), begin, len);
     579             :         }
     580             : 
     581             :         /*
     582             :          * Requested substring is partly in the left and partly in right child.
     583             :          * Create a rope of substrings for both childs.
     584             :          */
     585           0 :         MOZ_ASSERT(begin < rope->leftChild()->length() &&
     586             :                    begin + len > rope->leftChild()->length());
     587             : 
     588           0 :         size_t lhsLength = rope->leftChild()->length() - begin;
     589           0 :         size_t rhsLength = begin + len - rope->leftChild()->length();
     590             : 
     591           0 :         Rooted<JSRope*> ropeRoot(cx, rope);
     592           0 :         RootedString lhs(cx, NewDependentString(cx, ropeRoot->leftChild(), begin, lhsLength));
     593           0 :         if (!lhs)
     594           0 :             return nullptr;
     595             : 
     596           0 :         RootedString rhs(cx, NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength));
     597           0 :         if (!rhs)
     598           0 :             return nullptr;
     599             : 
     600           0 :         return JSRope::new_<CanGC>(cx, lhs, rhs, len);
     601             :     }
     602             : 
     603         177 :     return NewDependentString(cx, str, begin, len);
     604             : }
     605             : 
     606             : template <typename CharT> struct MaximumInlineLength;
     607             : 
     608             : template<> struct MaximumInlineLength<Latin1Char> {
     609             :     static constexpr size_t value = JSFatInlineString::MAX_LENGTH_LATIN1;
     610             : };
     611             : 
     612             : template<> struct MaximumInlineLength<char16_t> {
     613             :     static constexpr size_t value = JSFatInlineString::MAX_LENGTH_TWO_BYTE;
     614             : };
     615             : 
     616             : // Character buffer class used for ToLowerCase and ToUpperCase operations.
     617             : //
     618             : // Case conversion operations normally return a string with the same length as
     619             : // the input string. To avoid over-allocation, we optimistically allocate an
     620             : // array with same size as the input string and only when we detect special
     621             : // casing characters, which can change the output string length, we reallocate
     622             : // the output buffer to the final string length.
     623             : //
     624             : // As a further mean to improve runtime performance, the character buffer
     625             : // contains an inline storage, so we don't need to heap-allocate an array when
     626             : // a JSInlineString will be used for the output string.
     627             : //
     628             : // Why not use mozilla::Vector instead? mozilla::Vector doesn't provide enough
     629             : // fine-grained control to avoid over-allocation when (re)allocating for exact
     630             : // buffer sizes. This led to visible performance regressions in ยต-benchmarks.
     631             : template <typename CharT>
     632          84 : class MOZ_NON_PARAM InlineCharBuffer
     633             : {
     634             :     using CharPtr = UniquePtr<CharT[], JS::FreePolicy>;
     635             :     static constexpr size_t InlineCapacity = MaximumInlineLength<CharT>::value;
     636             : 
     637             :     CharT inlineStorage[InlineCapacity];
     638             :     CharPtr heapStorage;
     639             : 
     640             : #ifdef DEBUG
     641             :     // In debug mode, we keep track of the requested string lengths to ensure
     642             :     // all character buffer methods are called in the correct order and with
     643             :     // the expected argument values.
     644             :     size_t lastRequestedLength = 0;
     645             : 
     646          32 :     void assertValidRequest(size_t expectedLastLength, size_t length) {
     647          32 :         MOZ_ASSERT(length > expectedLastLength, "cannot shrink requested length");
     648          32 :         MOZ_ASSERT(lastRequestedLength == expectedLastLength);
     649          32 :         lastRequestedLength = length;
     650          32 :     }
     651             : #else
     652             :     void assertValidRequest(size_t expectedLastLength, size_t length) {}
     653             : #endif
     654             : 
     655             :   public:
     656          64 :     CharT* get()
     657             :     {
     658          64 :         return heapStorage ? heapStorage.get() : inlineStorage;
     659             :     }
     660             : 
     661          32 :     bool maybeAlloc(JSContext* cx, size_t length)
     662             :     {
     663          32 :         assertValidRequest(0, length);
     664             : 
     665          32 :         if (length <= InlineCapacity)
     666          30 :             return true;
     667             : 
     668           2 :         MOZ_ASSERT(!heapStorage, "heap storage already allocated");
     669           2 :         heapStorage = cx->make_pod_array<CharT>(length + 1);
     670           2 :         return !!heapStorage;
     671             :     }
     672             : 
     673           0 :     bool maybeRealloc(JSContext* cx, size_t oldLength, size_t newLength)
     674             :     {
     675           0 :         assertValidRequest(oldLength, newLength);
     676             : 
     677           0 :         if (newLength <= InlineCapacity)
     678           0 :             return true;
     679             : 
     680           0 :         if (!heapStorage) {
     681           0 :             heapStorage = cx->make_pod_array<CharT>(newLength + 1);
     682           0 :             if (!heapStorage)
     683           0 :                 return false;
     684             : 
     685           0 :             MOZ_ASSERT(oldLength <= InlineCapacity);
     686           0 :             PodCopy(heapStorage.get(), inlineStorage, oldLength);
     687           0 :             return true;
     688             :         }
     689             : 
     690           0 :         CharT* oldChars = heapStorage.release();
     691           0 :         CharT* newChars = cx->pod_realloc(oldChars, oldLength + 1, newLength + 1);
     692           0 :         if (!newChars) {
     693           0 :             js_free(oldChars);
     694           0 :             return false;
     695             :         }
     696             : 
     697           0 :         heapStorage.reset(newChars);
     698           0 :         return true;
     699             :     }
     700             : 
     701          32 :     JSString* toString(JSContext* cx, size_t length)
     702             :     {
     703          32 :         MOZ_ASSERT(length == lastRequestedLength);
     704             : 
     705          32 :         if (JSInlineString::lengthFits<CharT>(length)) {
     706          30 :             MOZ_ASSERT(!heapStorage,
     707             :                        "expected only inline storage when length fits in inline string");
     708             : 
     709          30 :             mozilla::Range<const CharT> range(inlineStorage, length);
     710          30 :             return NewInlineString<CanGC>(cx, range);
     711             :         }
     712             : 
     713           2 :         MOZ_ASSERT(heapStorage, "heap storage was not allocated for non-inline string");
     714             : 
     715           2 :         heapStorage.get()[length] = '\0'; // Null-terminate
     716           2 :         JSString* res = NewStringDontDeflate<CanGC>(cx, heapStorage.get(), length);
     717           2 :         if (!res)
     718           0 :             return nullptr;
     719             : 
     720           2 :         mozilla::Unused << heapStorage.release();
     721           2 :         return res;
     722             :     }
     723             : };
     724             : 
     725             : /**
     726             :  * U+03A3 GREEK CAPITAL LETTER SIGMA has two different lower case mappings
     727             :  * depending on its context:
     728             :  * When it's preceded by a cased character and not followed by another cased
     729             :  * character, its lower case form is U+03C2 GREEK SMALL LETTER FINAL SIGMA.
     730             :  * Otherwise its lower case mapping is U+03C3 GREEK SMALL LETTER SIGMA.
     731             :  *
     732             :  * Unicode 9.0, ยง3.13 Default Case Algorithms
     733             :  */
     734             : static char16_t
     735           0 : Final_Sigma(const char16_t* chars, size_t length, size_t index)
     736             : {
     737           0 :     MOZ_ASSERT(index < length);
     738           0 :     MOZ_ASSERT(chars[index] == unicode::GREEK_CAPITAL_LETTER_SIGMA);
     739           0 :     MOZ_ASSERT(unicode::ToLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA) ==
     740             :                unicode::GREEK_SMALL_LETTER_SIGMA);
     741             : 
     742             : #if ENABLE_INTL_API
     743             :     // Tell the analysis the BinaryProperty.contains function pointer called by
     744             :     // u_hasBinaryProperty cannot GC.
     745           0 :     JS::AutoSuppressGCAnalysis nogc;
     746             : 
     747           0 :     bool precededByCased = false;
     748           0 :     for (size_t i = index; i > 0; ) {
     749           0 :         char16_t c = chars[--i];
     750           0 :         uint32_t codePoint = c;
     751           0 :         if (unicode::IsTrailSurrogate(c) && i > 0) {
     752           0 :             char16_t lead = chars[i - 1];
     753           0 :             if (unicode::IsLeadSurrogate(lead)) {
     754           0 :                 codePoint = unicode::UTF16Decode(lead, c);
     755           0 :                 i--;
     756             :             }
     757             :         }
     758             : 
     759             :         // Ignore any characters with the property Case_Ignorable.
     760             :         // NB: We need to skip over all Case_Ignorable characters, even when
     761             :         // they also have the Cased binary property.
     762           0 :         if (u_hasBinaryProperty(codePoint, UCHAR_CASE_IGNORABLE))
     763           0 :             continue;
     764             : 
     765           0 :         precededByCased = u_hasBinaryProperty(codePoint, UCHAR_CASED);
     766           0 :         break;
     767             :     }
     768           0 :     if (!precededByCased)
     769           0 :         return unicode::GREEK_SMALL_LETTER_SIGMA;
     770             : 
     771           0 :     bool followedByCased = false;
     772           0 :     for (size_t i = index + 1; i < length; ) {
     773           0 :         char16_t c = chars[i++];
     774           0 :         uint32_t codePoint = c;
     775           0 :         if (unicode::IsLeadSurrogate(c) && i < length) {
     776           0 :             char16_t trail = chars[i];
     777           0 :             if (unicode::IsTrailSurrogate(trail)) {
     778           0 :                 codePoint = unicode::UTF16Decode(c, trail);
     779           0 :                 i++;
     780             :             }
     781             :         }
     782             : 
     783             :         // Ignore any characters with the property Case_Ignorable.
     784             :         // NB: We need to skip over all Case_Ignorable characters, even when
     785             :         // they also have the Cased binary property.
     786           0 :         if (u_hasBinaryProperty(codePoint, UCHAR_CASE_IGNORABLE))
     787           0 :             continue;
     788             : 
     789           0 :         followedByCased = u_hasBinaryProperty(codePoint, UCHAR_CASED);
     790           0 :         break;
     791             :     }
     792           0 :     if (!followedByCased)
     793           0 :         return unicode::GREEK_SMALL_LETTER_FINAL_SIGMA;
     794             : #endif
     795             : 
     796           0 :     return unicode::GREEK_SMALL_LETTER_SIGMA;
     797             : }
     798             : 
     799             : static Latin1Char
     800           0 : Final_Sigma(const Latin1Char* chars, size_t length, size_t index)
     801             : {
     802           0 :     MOZ_ASSERT_UNREACHABLE("U+03A3 is not a Latin-1 character");
     803             :     return 0;
     804             : }
     805             : 
     806             : // If |srcLength == destLength| is true, the destination buffer was allocated
     807             : // with the same size as the source buffer. When we append characters which
     808             : // have special casing mappings, we test |srcLength == destLength| to decide
     809             : // if we need to back out and reallocate a sufficiently large destination
     810             : // buffer. Otherwise the destination buffer was allocated with the correct
     811             : // size to hold all lower case mapped characters, i.e.
     812             : // |destLength == ToLowerCaseLength(srcChars, 0, srcLength)| is true.
     813             : template <typename CharT>
     814             : static size_t
     815          27 : ToLowerCaseImpl(CharT* destChars, const CharT* srcChars, size_t startIndex, size_t srcLength,
     816             :                 size_t destLength)
     817             : {
     818          27 :     MOZ_ASSERT(startIndex < srcLength);
     819          27 :     MOZ_ASSERT(srcLength <= destLength);
     820          26 :     MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), srcLength == destLength);
     821             : 
     822          27 :     size_t j = startIndex;
     823         245 :     for (size_t i = startIndex; i < srcLength; i++) {
     824         218 :         char16_t c = srcChars[i];
     825             :         if (!IsSame<CharT, Latin1Char>::value) {
     826           7 :             if (unicode::IsLeadSurrogate(c) && i + 1 < srcLength) {
     827           0 :                 char16_t trail = srcChars[i + 1];
     828           0 :                 if (unicode::IsTrailSurrogate(trail)) {
     829           0 :                     trail = unicode::ToLowerCaseNonBMPTrail(c, trail);
     830           0 :                     destChars[j++] = c;
     831           0 :                     destChars[j++] = trail;
     832           0 :                     i++;
     833           0 :                     continue;
     834             :                 }
     835             :             }
     836             : 
     837             :             // Special case: U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
     838             :             // lowercases to <U+0069 U+0307>.
     839           7 :             if (c == unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
     840             :                 // Return if the output buffer is too small.
     841           0 :                 if (srcLength == destLength)
     842           0 :                     return i;
     843             : 
     844           0 :                 destChars[j++] = CharT('i');
     845           0 :                 destChars[j++] = CharT(unicode::COMBINING_DOT_ABOVE);
     846           0 :                 continue;
     847             :             }
     848             : 
     849             :             // Special case: U+03A3 GREEK CAPITAL LETTER SIGMA lowercases to
     850             :             // one of two codepoints depending on context.
     851           7 :             if (c == unicode::GREEK_CAPITAL_LETTER_SIGMA) {
     852           0 :                 destChars[j++] = Final_Sigma(srcChars, srcLength, i);
     853           0 :                 continue;
     854             :             }
     855             :         }
     856             : 
     857         218 :         c = unicode::ToLowerCase(c);
     858         211 :         MOZ_ASSERT_IF((IsSame<CharT, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
     859         218 :         destChars[j++] = c;
     860             :     }
     861             : 
     862          27 :     MOZ_ASSERT(j == destLength);
     863          27 :     return srcLength;
     864             : }
     865             : 
     866             : static size_t
     867           0 : ToLowerCaseLength(const char16_t* chars, size_t startIndex, size_t length)
     868             : {
     869           0 :     size_t lowerLength = length;
     870           0 :     for (size_t i = startIndex; i < length; i++) {
     871           0 :         char16_t c = chars[i];
     872             : 
     873             :         // U+0130 is lowercased to the two-element sequence <U+0069 U+0307>.
     874           0 :         if (c == unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE)
     875           0 :             lowerLength += 1;
     876             :     }
     877           0 :     return lowerLength;
     878             : }
     879             : 
     880             : static size_t
     881           0 : ToLowerCaseLength(const Latin1Char* chars, size_t startIndex, size_t length)
     882             : {
     883           0 :     MOZ_ASSERT_UNREACHABLE("never called for Latin-1 strings");
     884             :     return 0;
     885             : }
     886             : 
     887             : template <typename CharT>
     888             : static JSString*
     889          37 : ToLowerCase(JSContext* cx, JSLinearString* str)
     890             : {
     891             :     // Unlike toUpperCase, toLowerCase has the nice invariant that if the
     892             :     // input is a Latin-1 string, the output is also a Latin-1 string.
     893             : 
     894          74 :     InlineCharBuffer<CharT> newChars;
     895             : 
     896          37 :     const size_t length = str->length();
     897             :     size_t resultLength;
     898             :     {
     899          64 :         AutoCheckCannotGC nogc;
     900          37 :         const CharT* chars = str->chars<CharT>(nogc);
     901             : 
     902             :         // We don't need extra special casing checks in the loop below,
     903             :         // because U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+03A3
     904             :         // GREEK CAPITAL LETTER SIGMA already have simple lower case mappings.
     905          37 :         MOZ_ASSERT(unicode::CanLowerCase(unicode::LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE),
     906             :                    "U+0130 has a simple lower case mapping");
     907          37 :         MOZ_ASSERT(unicode::CanLowerCase(unicode::GREEK_CAPITAL_LETTER_SIGMA),
     908             :                    "U+03A3 has a simple lower case mapping");
     909             : 
     910             :         // One element Latin-1 strings can be directly retrieved from the
     911             :         // static strings cache.
     912             :         if (IsSame<CharT, Latin1Char>::value) {
     913          28 :             if (length == 1) {
     914           0 :                 char16_t lower = unicode::ToLowerCase(chars[0]);
     915           0 :                 MOZ_ASSERT(lower <= JSString::MAX_LATIN1_CHAR);
     916           0 :                 MOZ_ASSERT(StaticStrings::hasUnit(lower));
     917             : 
     918           0 :                 return cx->staticStrings().getUnit(lower);
     919             :             }
     920             :         }
     921             : 
     922             :         // Look for the first character that changes when lowercased.
     923          37 :         size_t i = 0;
     924         161 :         for (; i < length; i++) {
     925          89 :             char16_t c = chars[i];
     926             :             if (!IsSame<CharT, Latin1Char>::value) {
     927          47 :                 if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
     928           0 :                     char16_t trail = chars[i + 1];
     929           0 :                     if (unicode::IsTrailSurrogate(trail)) {
     930           0 :                         if (unicode::CanLowerCaseNonBMP(c, trail))
     931           0 :                             break;
     932             : 
     933           0 :                         i++;
     934           0 :                         continue;
     935             :                     }
     936             :                 }
     937             :             }
     938          89 :             if (unicode::CanLowerCase(c))
     939          27 :                 break;
     940             :         }
     941             : 
     942             :         // If no character needs to change, return the input string.
     943          37 :         if (i == length)
     944          10 :             return str;
     945             : 
     946          27 :         resultLength = length;
     947          27 :         if (!newChars.maybeAlloc(cx, resultLength))
     948           0 :             return nullptr;
     949             : 
     950          27 :         PodCopy(newChars.get(), chars, i);
     951             : 
     952          27 :         size_t readChars = ToLowerCaseImpl(newChars.get(), chars, i, length, resultLength);
     953          27 :         if (readChars < length) {
     954           0 :             MOZ_ASSERT((!IsSame<CharT, Latin1Char>::value),
     955             :                        "Latin-1 strings don't have special lower case mappings");
     956           0 :             resultLength = ToLowerCaseLength(chars, readChars, length);
     957             : 
     958           0 :             if (!newChars.maybeRealloc(cx, length, resultLength))
     959           0 :                 return nullptr;
     960             : 
     961           0 :             MOZ_ALWAYS_TRUE(length ==
     962             :                 ToLowerCaseImpl(newChars.get(), chars, readChars, length, resultLength));
     963             :         }
     964             :     }
     965             : 
     966          27 :     return newChars.toString(cx, resultLength);
     967             : }
     968             : 
     969             : JSString*
     970          37 : js::StringToLowerCase(JSContext* cx, HandleLinearString string)
     971             : {
     972          37 :     if (string->hasLatin1Chars())
     973          28 :         return ToLowerCase<Latin1Char>(cx, string);
     974           9 :     return ToLowerCase<char16_t>(cx, string);
     975             : }
     976             : 
     977             : bool
     978          37 : js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp)
     979             : {
     980          37 :     CallArgs args = CallArgsFromVp(argc, vp);
     981             : 
     982          74 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
     983          37 :     if (!str)
     984           0 :         return false;
     985             : 
     986          74 :     RootedLinearString linear(cx, str->ensureLinear(cx));
     987          37 :     if (!linear)
     988           0 :         return false;
     989             : 
     990          37 :     JSString* result = StringToLowerCase(cx, linear);
     991          37 :     if (!result)
     992           0 :         return false;
     993             : 
     994          37 :     args.rval().setString(result);
     995          37 :     return true;
     996             : }
     997             : 
     998             : #if !EXPOSE_INTL_API
     999             : bool
    1000             : js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
    1001             : {
    1002             :     CallArgs args = CallArgsFromVp(argc, vp);
    1003             : 
    1004             :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    1005             :     if (!str)
    1006             :         return false;
    1007             : 
    1008             :     /*
    1009             :      * Forcefully ignore the first (or any) argument and return toLowerCase(),
    1010             :      * ECMA has reserved that argument, presumably for defining the locale.
    1011             :      */
    1012             :     if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) {
    1013             :         RootedValue result(cx);
    1014             :         if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result))
    1015             :             return false;
    1016             : 
    1017             :         args.rval().set(result);
    1018             :         return true;
    1019             :     }
    1020             : 
    1021             :     RootedLinearString linear(cx, str->ensureLinear(cx));
    1022             :     if (!linear)
    1023             :         return false;
    1024             : 
    1025             :     JSString* result = StringToLowerCase(cx, linear);
    1026             :     if (!result)
    1027             :         return false;
    1028             : 
    1029             :     args.rval().setString(result);
    1030             :     return true;
    1031             : }
    1032             : #endif /* !EXPOSE_INTL_API */
    1033             : 
    1034             : static inline bool
    1035           0 : CanUpperCaseSpecialCasing(Latin1Char charCode)
    1036             : {
    1037             :     // Handle U+00DF LATIN SMALL LETTER SHARP S inline, all other Latin-1
    1038             :     // characters don't have special casing rules.
    1039           0 :     MOZ_ASSERT_IF(charCode != unicode::LATIN_SMALL_LETTER_SHARP_S,
    1040             :                   !unicode::CanUpperCaseSpecialCasing(charCode));
    1041             : 
    1042           0 :     return charCode == unicode::LATIN_SMALL_LETTER_SHARP_S;
    1043             : }
    1044             : 
    1045             : static inline bool
    1046           0 : CanUpperCaseSpecialCasing(char16_t charCode)
    1047             : {
    1048           0 :     return unicode::CanUpperCaseSpecialCasing(charCode);
    1049             : }
    1050             : 
    1051             : static inline size_t
    1052           0 : LengthUpperCaseSpecialCasing(Latin1Char charCode)
    1053             : {
    1054             :     // U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
    1055           0 :     MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
    1056             : 
    1057           0 :     return 2;
    1058             : }
    1059             : 
    1060             : static inline size_t
    1061           0 : LengthUpperCaseSpecialCasing(char16_t charCode)
    1062             : {
    1063           0 :     MOZ_ASSERT(CanUpperCaseSpecialCasing(charCode));
    1064             : 
    1065           0 :     return unicode::LengthUpperCaseSpecialCasing(charCode);
    1066             : }
    1067             : 
    1068             : static inline void
    1069           0 : AppendUpperCaseSpecialCasing(char16_t charCode, Latin1Char* elements, size_t* index)
    1070             : {
    1071             :     // U+00DF LATIN SMALL LETTER SHARP S is uppercased to two 'S'.
    1072           0 :     MOZ_ASSERT(charCode == unicode::LATIN_SMALL_LETTER_SHARP_S);
    1073             :     static_assert('S' <= JSString::MAX_LATIN1_CHAR, "'S' is a Latin-1 character");
    1074             : 
    1075           0 :     elements[(*index)++] = 'S';
    1076           0 :     elements[(*index)++] = 'S';
    1077           0 : }
    1078             : 
    1079             : static inline void
    1080           0 : AppendUpperCaseSpecialCasing(char16_t charCode, char16_t* elements, size_t* index)
    1081             : {
    1082           0 :     unicode::AppendUpperCaseSpecialCasing(charCode, elements, index);
    1083           0 : }
    1084             : 
    1085             : // See ToLowerCaseImpl for an explanation of the parameters.
    1086             : template <typename DestChar, typename SrcChar>
    1087             : static size_t
    1088           5 : ToUpperCaseImpl(DestChar* destChars, const SrcChar* srcChars, size_t startIndex, size_t srcLength,
    1089             :                 size_t destLength)
    1090             : {
    1091             :     static_assert(IsSame<SrcChar, Latin1Char>::value || !IsSame<DestChar, Latin1Char>::value,
    1092             :                   "cannot write non-Latin-1 characters into Latin-1 string");
    1093           5 :     MOZ_ASSERT(startIndex < srcLength);
    1094           5 :     MOZ_ASSERT(srcLength <= destLength);
    1095             : 
    1096           5 :     size_t j = startIndex;
    1097          63 :     for (size_t i = startIndex; i < srcLength; i++) {
    1098          58 :         char16_t c = srcChars[i];
    1099             :         if (!IsSame<DestChar, Latin1Char>::value) {
    1100           0 :             if (unicode::IsLeadSurrogate(c) && i + 1 < srcLength) {
    1101           0 :                 char16_t trail = srcChars[i + 1];
    1102           0 :                 if (unicode::IsTrailSurrogate(trail)) {
    1103           0 :                     trail = unicode::ToUpperCaseNonBMPTrail(c, trail);
    1104           0 :                     destChars[j++] = c;
    1105           0 :                     destChars[j++] = trail;
    1106           0 :                     i++;
    1107           0 :                     continue;
    1108             :                 }
    1109             :             }
    1110             :         }
    1111             : 
    1112          58 :         if (MOZ_UNLIKELY(c > 0x7f && CanUpperCaseSpecialCasing(static_cast<SrcChar>(c)))) {
    1113             :             // Return if the output buffer is too small.
    1114           0 :             if (srcLength == destLength)
    1115           0 :                 return i;
    1116             : 
    1117           0 :             AppendUpperCaseSpecialCasing(c, destChars, &j);
    1118           0 :             continue;
    1119             :         }
    1120             : 
    1121          58 :         c = unicode::ToUpperCase(c);
    1122          58 :         MOZ_ASSERT_IF((IsSame<DestChar, Latin1Char>::value), c <= JSString::MAX_LATIN1_CHAR);
    1123          58 :         destChars[j++] = c;
    1124             :     }
    1125             : 
    1126           5 :     MOZ_ASSERT(j == destLength);
    1127           5 :     return srcLength;
    1128             : }
    1129             : 
    1130             : // Explicit instantiation so we don't hit the static_assert from above.
    1131             : static bool
    1132           0 : ToUpperCaseImpl(Latin1Char* destChars, const char16_t* srcChars, size_t startIndex,
    1133             :                 size_t srcLength, size_t destLength)
    1134             : {
    1135           0 :     MOZ_ASSERT_UNREACHABLE("cannot write non-Latin-1 characters into Latin-1 string");
    1136             :     return false;
    1137             : }
    1138             : 
    1139             : template <typename CharT>
    1140             : static size_t
    1141           0 : ToUpperCaseLength(const CharT* chars, size_t startIndex, size_t length)
    1142             : {
    1143           0 :     size_t upperLength = length;
    1144           0 :     for (size_t i = startIndex; i < length; i++) {
    1145           0 :         char16_t c = chars[i];
    1146             : 
    1147           0 :         if (c > 0x7f && CanUpperCaseSpecialCasing(static_cast<CharT>(c)))
    1148           0 :             upperLength += LengthUpperCaseSpecialCasing(static_cast<CharT>(c)) - 1;
    1149             :     }
    1150           0 :     return upperLength;
    1151             : }
    1152             : 
    1153             : template <typename DestChar, typename SrcChar>
    1154             : static inline void
    1155           0 : CopyChars(DestChar* destChars, const SrcChar* srcChars, size_t length)
    1156             : {
    1157             :     static_assert(!IsSame<DestChar, SrcChar>::value, "PodCopy is used for the same type case");
    1158           0 :     for (size_t i = 0; i < length; i++)
    1159           0 :         destChars[i] = srcChars[i];
    1160           0 : }
    1161             : 
    1162             : template <typename CharT>
    1163             : static inline void
    1164           5 : CopyChars(CharT* destChars, const CharT* srcChars, size_t length)
    1165             : {
    1166           5 :     PodCopy(destChars, srcChars, length);
    1167           5 : }
    1168             : 
    1169             : template <typename DestChar, typename SrcChar>
    1170             : static inline bool
    1171           5 : ToUpperCase(JSContext* cx, InlineCharBuffer<DestChar>& newChars, const SrcChar* chars,
    1172             :             size_t startIndex, size_t length, size_t* resultLength)
    1173             : {
    1174           5 :     MOZ_ASSERT(startIndex < length);
    1175             : 
    1176           5 :     *resultLength = length;
    1177           5 :     if (!newChars.maybeAlloc(cx, length))
    1178           0 :         return false;
    1179             : 
    1180           5 :     CopyChars(newChars.get(), chars, startIndex);
    1181             : 
    1182           5 :     size_t readChars = ToUpperCaseImpl(newChars.get(), chars, startIndex, length, length);
    1183           5 :     if (readChars < length) {
    1184           0 :         size_t actualLength = ToUpperCaseLength(chars, readChars, length);
    1185             : 
    1186           0 :         *resultLength = actualLength;
    1187           0 :         if (!newChars.maybeRealloc(cx, length, actualLength))
    1188           0 :             return false;
    1189             : 
    1190           0 :         MOZ_ALWAYS_TRUE(length ==
    1191             :             ToUpperCaseImpl(newChars.get(), chars, readChars, length, actualLength));
    1192             :     }
    1193             : 
    1194           5 :     return true;
    1195             : }
    1196             : 
    1197             : template <typename CharT>
    1198             : static JSString*
    1199           6 : ToUpperCase(JSContext* cx, JSLinearString* str)
    1200             : {
    1201             :     using Latin1Buffer = InlineCharBuffer<Latin1Char>;
    1202             :     using TwoByteBuffer = InlineCharBuffer<char16_t>;
    1203             : 
    1204          12 :     mozilla::MaybeOneOf<Latin1Buffer, TwoByteBuffer> newChars;
    1205           6 :     const size_t length = str->length();
    1206             :     size_t resultLength;
    1207             :     {
    1208          11 :         AutoCheckCannotGC nogc;
    1209           6 :         const CharT* chars = str->chars<CharT>(nogc);
    1210             : 
    1211             :         // Most one element Latin-1 strings can be directly retrieved from the
    1212             :         // static strings cache.
    1213             :         if (IsSame<CharT, Latin1Char>::value) {
    1214           6 :             if (length == 1) {
    1215           1 :                 Latin1Char c = chars[0];
    1216           1 :                 if (c != unicode::MICRO_SIGN &&
    1217           1 :                     c != unicode::LATIN_SMALL_LETTER_Y_WITH_DIAERESIS &&
    1218             :                     c != unicode::LATIN_SMALL_LETTER_SHARP_S)
    1219             :                 {
    1220           1 :                     char16_t upper = unicode::ToUpperCase(c);
    1221           1 :                     MOZ_ASSERT(upper <= JSString::MAX_LATIN1_CHAR);
    1222           1 :                     MOZ_ASSERT(StaticStrings::hasUnit(upper));
    1223             : 
    1224           1 :                     return cx->staticStrings().getUnit(upper);
    1225             :                 }
    1226             : 
    1227           0 :                 MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR ||
    1228             :                            CanUpperCaseSpecialCasing(c));
    1229             :             }
    1230             :         }
    1231             : 
    1232             :         // Look for the first character that changes when uppercased.
    1233           5 :         size_t i = 0;
    1234          13 :         for (; i < length; i++) {
    1235           9 :             char16_t c = chars[i];
    1236             :             if (!IsSame<CharT, Latin1Char>::value) {
    1237           0 :                 if (unicode::IsLeadSurrogate(c) && i + 1 < length) {
    1238           0 :                     char16_t trail = chars[i + 1];
    1239           0 :                     if (unicode::IsTrailSurrogate(trail)) {
    1240           0 :                         if (unicode::CanUpperCaseNonBMP(c, trail))
    1241           0 :                             break;
    1242             : 
    1243           0 :                         i++;
    1244           0 :                         continue;
    1245             :                     }
    1246             :                 }
    1247             :             }
    1248           9 :             if (unicode::CanUpperCase(c))
    1249           5 :                 break;
    1250           4 :             if (MOZ_UNLIKELY(c > 0x7f && CanUpperCaseSpecialCasing(static_cast<CharT>(c))))
    1251           0 :                 break;
    1252             :         }
    1253             : 
    1254             :         // If no character needs to change, return the input string.
    1255           5 :         if (i == length)
    1256           0 :             return str;
    1257             : 
    1258             :         // The string changes when uppercased, so we must create a new string.
    1259             :         // Can it be Latin-1?
    1260             :         //
    1261             :         // If the original string is Latin-1, it can -- unless the string
    1262             :         // contains U+00B5 MICRO SIGN or U+00FF SMALL LETTER Y WITH DIAERESIS,
    1263             :         // the only Latin-1 codepoints that don't uppercase within Latin-1.
    1264             :         // Search for those codepoints to decide whether the new string can be
    1265             :         // Latin-1.
    1266             :         // If the original string is a two-byte string, its uppercase form is
    1267             :         // so rarely Latin-1 that we don't even consider creating a new
    1268             :         // Latin-1 string.
    1269             :         bool resultIsLatin1;
    1270             :         if (IsSame<CharT, Latin1Char>::value) {
    1271           5 :             resultIsLatin1 = true;
    1272          63 :             for (size_t j = i; j < length; j++) {
    1273          58 :                 Latin1Char c = chars[j];
    1274          58 :                 if (c == unicode::MICRO_SIGN ||
    1275             :                     c == unicode::LATIN_SMALL_LETTER_Y_WITH_DIAERESIS)
    1276             :                 {
    1277           0 :                     MOZ_ASSERT(unicode::ToUpperCase(c) > JSString::MAX_LATIN1_CHAR);
    1278           0 :                     resultIsLatin1 = false;
    1279           0 :                     break;
    1280             :                 } else {
    1281          58 :                     MOZ_ASSERT(unicode::ToUpperCase(c) <= JSString::MAX_LATIN1_CHAR);
    1282             :                 }
    1283             :             }
    1284             :         } else {
    1285           0 :             resultIsLatin1 = false;
    1286             :         }
    1287             : 
    1288           5 :         if (resultIsLatin1) {
    1289           5 :             newChars.construct<Latin1Buffer>();
    1290             : 
    1291           5 :             if (!ToUpperCase(cx, newChars.ref<Latin1Buffer>(), chars, i, length, &resultLength))
    1292           0 :                 return nullptr;
    1293             :         } else {
    1294           0 :             newChars.construct<TwoByteBuffer>();
    1295             : 
    1296           0 :             if (!ToUpperCase(cx, newChars.ref<TwoByteBuffer>(), chars, i, length, &resultLength))
    1297           0 :                 return nullptr;
    1298             :         }
    1299             :     }
    1300             : 
    1301           5 :     return newChars.constructed<Latin1Buffer>()
    1302           5 :            ? newChars.ref<Latin1Buffer>().toString(cx, resultLength)
    1303          10 :            : newChars.ref<TwoByteBuffer>().toString(cx, resultLength);
    1304             : }
    1305             : 
    1306             : JSString*
    1307           6 : js::StringToUpperCase(JSContext* cx, HandleLinearString string)
    1308             : {
    1309           6 :     if (string->hasLatin1Chars())
    1310           6 :         return ToUpperCase<Latin1Char>(cx, string);
    1311           0 :     return ToUpperCase<char16_t>(cx, string);
    1312             : }
    1313             : 
    1314             : bool
    1315           6 : js::str_toUpperCase(JSContext* cx, unsigned argc, Value* vp)
    1316             : {
    1317           6 :     CallArgs args = CallArgsFromVp(argc, vp);
    1318             : 
    1319          12 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    1320           6 :     if (!str)
    1321           0 :         return false;
    1322             : 
    1323          12 :     RootedLinearString linear(cx, str->ensureLinear(cx));
    1324           6 :     if (!linear)
    1325           0 :         return false;
    1326             : 
    1327           6 :     JSString* result = StringToUpperCase(cx, linear);
    1328           6 :     if (!result)
    1329           0 :         return false;
    1330             : 
    1331           6 :     args.rval().setString(result);
    1332           6 :     return true;
    1333             : }
    1334             : 
    1335             : #if !EXPOSE_INTL_API
    1336             : bool
    1337             : js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
    1338             : {
    1339             :     CallArgs args = CallArgsFromVp(argc, vp);
    1340             : 
    1341             :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    1342             :     if (!str)
    1343             :         return false;
    1344             : 
    1345             :     /*
    1346             :      * Forcefully ignore the first (or any) argument and return toUpperCase(),
    1347             :      * ECMA has reserved that argument, presumably for defining the locale.
    1348             :      */
    1349             :     if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) {
    1350             :         RootedValue result(cx);
    1351             :         if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result))
    1352             :             return false;
    1353             : 
    1354             :         args.rval().set(result);
    1355             :         return true;
    1356             :     }
    1357             : 
    1358             :     RootedLinearString linear(cx, str->ensureLinear(cx));
    1359             :     if (!linear)
    1360             :         return false;
    1361             : 
    1362             :     JSString* result = StringToUpperCase(cx, linear);
    1363             :     if (!result)
    1364             :         return false;
    1365             : 
    1366             :     args.rval().setString(result);
    1367             :     return true;
    1368             : }
    1369             : #endif /* !EXPOSE_INTL_API */
    1370             : 
    1371             : #if !EXPOSE_INTL_API
    1372             : bool
    1373             : js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
    1374             : {
    1375             :     CallArgs args = CallArgsFromVp(argc, vp);
    1376             :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    1377             :     if (!str)
    1378             :         return false;
    1379             : 
    1380             :     RootedString thatStr(cx, ToString<CanGC>(cx, args.get(0)));
    1381             :     if (!thatStr)
    1382             :         return false;
    1383             : 
    1384             :     if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeCompare) {
    1385             :         RootedValue result(cx);
    1386             :         if (!cx->runtime()->localeCallbacks->localeCompare(cx, str, thatStr, &result))
    1387             :             return false;
    1388             : 
    1389             :         args.rval().set(result);
    1390             :         return true;
    1391             :     }
    1392             : 
    1393             :     int32_t result;
    1394             :     if (!CompareStrings(cx, str, thatStr, &result))
    1395             :         return false;
    1396             : 
    1397             :     args.rval().setInt32(result);
    1398             :     return true;
    1399             : }
    1400             : #endif
    1401             : 
    1402             : #if EXPOSE_INTL_API
    1403             : // ES2017 draft rev 45e890512fd77add72cc0ee742785f9f6f6482de
    1404             : // 21.1.3.12 String.prototype.normalize ( [ form ] )
    1405             : bool
    1406           0 : js::str_normalize(JSContext* cx, unsigned argc, Value* vp)
    1407             : {
    1408           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    1409             : 
    1410             :     // Steps 1-2.
    1411           0 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    1412           0 :     if (!str)
    1413           0 :         return false;
    1414             : 
    1415             :     enum NormalizationForm {
    1416             :         NFC, NFD, NFKC, NFKD
    1417             :     };
    1418             : 
    1419             :     NormalizationForm form;
    1420           0 :     if (!args.hasDefined(0)) {
    1421             :         // Step 3.
    1422           0 :         form = NFC;
    1423             :     } else {
    1424             :         // Step 4.
    1425           0 :         RootedLinearString formStr(cx, ArgToRootedString(cx, args, 0));
    1426           0 :         if (!formStr)
    1427           0 :             return false;
    1428             : 
    1429             :         // Step 5.
    1430           0 :         if (EqualStrings(formStr, cx->names().NFC)) {
    1431           0 :             form = NFC;
    1432           0 :         } else if (EqualStrings(formStr, cx->names().NFD)) {
    1433           0 :             form = NFD;
    1434           0 :         } else if (EqualStrings(formStr, cx->names().NFKC)) {
    1435           0 :             form = NFKC;
    1436           0 :         } else if (EqualStrings(formStr, cx->names().NFKD)) {
    1437           0 :             form = NFKD;
    1438             :         } else {
    1439           0 :             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_NORMALIZE_FORM);
    1440           0 :             return false;
    1441             :         }
    1442             :     }
    1443             : 
    1444             :     // Latin-1 strings are already in Normalization Form C.
    1445           0 :     if (form == NFC && str->hasLatin1Chars()) {
    1446             :         // Step 7.
    1447           0 :         args.rval().setString(str);
    1448           0 :         return true;
    1449             :     }
    1450             : 
    1451             :     // Step 6.
    1452           0 :     AutoStableStringChars stableChars(cx);
    1453           0 :     if (!stableChars.initTwoByte(cx, str))
    1454           0 :         return false;
    1455             : 
    1456           0 :     mozilla::Range<const char16_t> srcChars = stableChars.twoByteRange();
    1457             : 
    1458             :     // The unorm2_getXXXInstance() methods return a shared instance which must
    1459             :     // not be deleted.
    1460           0 :     UErrorCode status = U_ZERO_ERROR;
    1461             :     const UNormalizer2* normalizer;
    1462           0 :     if (form == NFC) {
    1463           0 :         normalizer = unorm2_getNFCInstance(&status);
    1464           0 :     } else if (form == NFD) {
    1465           0 :         normalizer = unorm2_getNFDInstance(&status);
    1466           0 :     } else if (form == NFKC) {
    1467           0 :         normalizer = unorm2_getNFKCInstance(&status);
    1468             :     } else {
    1469           0 :         MOZ_ASSERT(form == NFKD);
    1470           0 :         normalizer = unorm2_getNFKDInstance(&status);
    1471             :     }
    1472           0 :     if (U_FAILURE(status)) {
    1473           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
    1474           0 :         return false;
    1475             :     }
    1476             : 
    1477           0 :     int32_t spanLength = unorm2_spanQuickCheckYes(normalizer,
    1478           0 :                                                   srcChars.begin().get(), srcChars.length(),
    1479           0 :                                                   &status);
    1480           0 :     if (U_FAILURE(status)) {
    1481           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
    1482           0 :         return false;
    1483             :     }
    1484           0 :     MOZ_ASSERT(0 <= spanLength && size_t(spanLength) <= srcChars.length());
    1485             : 
    1486             :     // Return if the input string is already normalized.
    1487           0 :     if (size_t(spanLength) == srcChars.length()) {
    1488             :         // Step 7.
    1489           0 :         args.rval().setString(str);
    1490           0 :         return true;
    1491             :     }
    1492             : 
    1493             :     static const size_t INLINE_CAPACITY = 32;
    1494             : 
    1495           0 :     Vector<char16_t, INLINE_CAPACITY> chars(cx);
    1496           0 :     if (!chars.resize(Max(INLINE_CAPACITY, srcChars.length())))
    1497           0 :         return false;
    1498             : 
    1499             :     // Copy the already normalized prefix.
    1500           0 :     if (spanLength > 0)
    1501           0 :         PodCopy(chars.begin(), srcChars.begin().get(), size_t(spanLength));
    1502             : 
    1503           0 :     mozilla::RangedPtr<const char16_t> remainingStart = srcChars.begin() + spanLength;
    1504           0 :     size_t remainingLength = srcChars.length() - size_t(spanLength);
    1505             : 
    1506           0 :     int32_t size = unorm2_normalizeSecondAndAppend(normalizer,
    1507           0 :                                                    chars.begin(), spanLength, chars.length(),
    1508           0 :                                                    remainingStart.get(), remainingLength, &status);
    1509           0 :     if (status == U_BUFFER_OVERFLOW_ERROR) {
    1510           0 :         MOZ_ASSERT(size >= 0);
    1511           0 :         if (!chars.resize(size))
    1512           0 :             return false;
    1513           0 :         status = U_ZERO_ERROR;
    1514             : #ifdef DEBUG
    1515             :         int32_t finalSize =
    1516             : #endif
    1517           0 :         unorm2_normalizeSecondAndAppend(normalizer,
    1518           0 :                                         chars.begin(), spanLength, chars.length(),
    1519           0 :                                         remainingStart.get(), remainingLength, &status);
    1520           0 :         MOZ_ASSERT_IF(!U_FAILURE(status), size == finalSize);
    1521             :     }
    1522           0 :     if (U_FAILURE(status)) {
    1523           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
    1524           0 :         return false;
    1525             :     }
    1526             : 
    1527           0 :     MOZ_ASSERT(size >= 0);
    1528           0 :     JSString* ns = NewStringCopyN<CanGC>(cx, chars.begin(), size);
    1529           0 :     if (!ns)
    1530           0 :         return false;
    1531             : 
    1532             :     // Step 7.
    1533           0 :     args.rval().setString(ns);
    1534           0 :     return true;
    1535             : }
    1536             : #endif
    1537             : 
    1538             : bool
    1539           0 : js::str_charAt(JSContext* cx, unsigned argc, Value* vp)
    1540             : {
    1541           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    1542             : 
    1543           0 :     RootedString str(cx);
    1544             :     size_t i;
    1545           0 :     if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
    1546           0 :         str = args.thisv().toString();
    1547           0 :         i = size_t(args[0].toInt32());
    1548           0 :         if (i >= str->length())
    1549           0 :             goto out_of_range;
    1550             :     } else {
    1551           0 :         str = ToStringForStringFunction(cx, args.thisv());
    1552           0 :         if (!str)
    1553           0 :             return false;
    1554             : 
    1555           0 :         double d = 0.0;
    1556           0 :         if (args.length() > 0 && !ToInteger(cx, args[0], &d))
    1557           0 :             return false;
    1558             : 
    1559           0 :         if (d < 0 || str->length() <= d)
    1560           0 :             goto out_of_range;
    1561           0 :         i = size_t(d);
    1562             :     }
    1563             : 
    1564           0 :     str = cx->staticStrings().getUnitStringForElement(cx, str, i);
    1565           0 :     if (!str)
    1566           0 :         return false;
    1567           0 :     args.rval().setString(str);
    1568           0 :     return true;
    1569             : 
    1570             :   out_of_range:
    1571           0 :     args.rval().setString(cx->runtime()->emptyString);
    1572           0 :     return true;
    1573             : }
    1574             : 
    1575             : bool
    1576       14324 : js::str_charCodeAt_impl(JSContext* cx, HandleString string, HandleValue index, MutableHandleValue res)
    1577             : {
    1578       28648 :     RootedString str(cx);
    1579             :     size_t i;
    1580       14324 :     if (index.isInt32()) {
    1581       14324 :         i = index.toInt32();
    1582       14324 :         if (i >= string->length())
    1583           0 :             goto out_of_range;
    1584             :     } else {
    1585           0 :         double d = 0.0;
    1586           0 :         if (!ToInteger(cx, index, &d))
    1587           0 :             return false;
    1588             :         // check whether d is negative as size_t is unsigned
    1589           0 :         if (d < 0 || string->length() <= d )
    1590           0 :             goto out_of_range;
    1591           0 :         i = size_t(d);
    1592             :     }
    1593             :     char16_t c;
    1594       14324 :     if (!string->getChar(cx, i , &c))
    1595           0 :         return false;
    1596       14324 :     res.setInt32(c);
    1597       14324 :     return true;
    1598             : 
    1599             : out_of_range:
    1600           0 :     res.setNaN();
    1601           0 :     return true;
    1602             : }
    1603             : 
    1604             : bool
    1605       14324 : js::str_charCodeAt(JSContext* cx, unsigned argc, Value* vp)
    1606             : {
    1607       14324 :     CallArgs args = CallArgsFromVp(argc, vp);
    1608       28648 :     RootedString str(cx);
    1609       28648 :     RootedValue index(cx);
    1610       14324 :     if (args.thisv().isString()) {
    1611       14324 :         str = args.thisv().toString();
    1612             :     } else {
    1613           0 :         str = ToStringForStringFunction(cx, args.thisv());
    1614           0 :         if (!str)
    1615           0 :             return false;
    1616             :     }
    1617       14324 :     if (args.length() != 0)
    1618       14324 :         index = args[0];
    1619             :     else
    1620           0 :         index.setInt32(0);
    1621             : 
    1622       14324 :     return js::str_charCodeAt_impl(cx, str, index, args.rval());
    1623             : }
    1624             : 
    1625             : /*
    1626             :  * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen.
    1627             :  * The patlen argument must be positive and no greater than sBMHPatLenMax.
    1628             :  *
    1629             :  * Return the index of pat in text, or -1 if not found.
    1630             :  */
    1631             : static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */
    1632             : static const uint32_t sBMHPatLenMax   = 255; /* skip table element is uint8_t */
    1633             : static const int      sBMHBadPattern  = -2;  /* return value if pat is not ISO-Latin-1 */
    1634             : 
    1635             : template <typename TextChar, typename PatChar>
    1636             : static int
    1637           0 : BoyerMooreHorspool(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t patLen)
    1638             : {
    1639           0 :     MOZ_ASSERT(0 < patLen && patLen <= sBMHPatLenMax);
    1640             : 
    1641             :     uint8_t skip[sBMHCharSetSize];
    1642           0 :     for (uint32_t i = 0; i < sBMHCharSetSize; i++)
    1643           0 :         skip[i] = uint8_t(patLen);
    1644             : 
    1645           0 :     uint32_t patLast = patLen - 1;
    1646           0 :     for (uint32_t i = 0; i < patLast; i++) {
    1647           0 :         char16_t c = pat[i];
    1648           0 :         if (c >= sBMHCharSetSize)
    1649           0 :             return sBMHBadPattern;
    1650           0 :         skip[c] = uint8_t(patLast - i);
    1651             :     }
    1652             : 
    1653           0 :     for (uint32_t k = patLast; k < textLen; ) {
    1654           0 :         for (uint32_t i = k, j = patLast; ; i--, j--) {
    1655           0 :             if (text[i] != pat[j])
    1656           0 :                 break;
    1657           0 :             if (j == 0)
    1658           0 :                 return static_cast<int>(i);  /* safe: max string size */
    1659             :         }
    1660             : 
    1661           0 :         char16_t c = text[k];
    1662           0 :         k += (c >= sBMHCharSetSize) ? patLen : skip[c];
    1663             :     }
    1664           0 :     return -1;
    1665             : }
    1666             : 
    1667             : template <typename TextChar, typename PatChar>
    1668             : struct MemCmp {
    1669             :     typedef uint32_t Extent;
    1670             :     static MOZ_ALWAYS_INLINE Extent computeExtent(const PatChar*, uint32_t patLen) {
    1671             :         return (patLen - 1) * sizeof(PatChar);
    1672             :     }
    1673             :     static MOZ_ALWAYS_INLINE bool match(const PatChar* p, const TextChar* t, Extent extent) {
    1674             :         MOZ_ASSERT(sizeof(TextChar) == sizeof(PatChar));
    1675             :         return memcmp(p, t, extent) == 0;
    1676             :     }
    1677             : };
    1678             : 
    1679             : template <typename TextChar, typename PatChar>
    1680             : struct ManualCmp {
    1681             :     typedef const PatChar* Extent;
    1682         174 :     static MOZ_ALWAYS_INLINE Extent computeExtent(const PatChar* pat, uint32_t patLen) {
    1683         174 :         return pat + patLen;
    1684             :     }
    1685         291 :     static MOZ_ALWAYS_INLINE bool match(const PatChar* p, const TextChar* t, Extent extent) {
    1686         513 :         for (; p != extent; ++p, ++t) {
    1687         257 :             if (*p != *t)
    1688          35 :                 return false;
    1689             :         }
    1690          34 :         return true;
    1691             :     }
    1692             : };
    1693             : 
    1694             : template <typename TextChar, typename PatChar>
    1695             : static const TextChar*
    1696          50 : FirstCharMatcherUnrolled(const TextChar* text, uint32_t n, const PatChar pat)
    1697             : {
    1698          50 :     const TextChar* textend = text + n;
    1699          50 :     const TextChar* t = text;
    1700             : 
    1701          50 :     switch ((textend - t) & 7) {
    1702           4 :         case 0: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1703           8 :         case 7: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1704          14 :         case 6: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1705          22 :         case 5: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1706          27 :         case 4: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1707          31 :         case 3: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1708          34 :         case 2: if (*t++ == pat) return t - 1; MOZ_FALLTHROUGH;
    1709          43 :         case 1: if (*t++ == pat) return t - 1;
    1710             :     }
    1711         230 :     while (textend != t) {
    1712         111 :         if (t[0] == pat) return t;
    1713         111 :         if (t[1] == pat) return t + 1;
    1714         108 :         if (t[2] == pat) return t + 2;
    1715         106 :         if (t[3] == pat) return t + 3;
    1716         104 :         if (t[4] == pat) return t + 4;
    1717         104 :         if (t[5] == pat) return t + 5;
    1718         103 :         if (t[6] == pat) return t + 6;
    1719          97 :         if (t[7] == pat) return t + 7;
    1720          95 :         t += 8;
    1721             :     }
    1722          24 :     return nullptr;
    1723             : }
    1724             : 
    1725             : static const char*
    1726         157 : FirstCharMatcher8bit(const char* text, uint32_t n, const char pat)
    1727             : {
    1728         157 :     return reinterpret_cast<const char*>(memchr(text, pat, n));
    1729             : }
    1730             : 
    1731             : template <class InnerMatch, typename TextChar, typename PatChar>
    1732             : static int
    1733         174 : Matcher(const TextChar* text, uint32_t textlen, const PatChar* pat, uint32_t patlen)
    1734             : {
    1735         174 :     MOZ_ASSERT(patlen > 0);
    1736             : 
    1737           0 :     if (sizeof(TextChar) == 1 && sizeof(PatChar) > 1 && pat[0] > 0xff)
    1738           0 :         return -1;
    1739             : 
    1740         174 :     const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
    1741             : 
    1742         174 :     uint32_t i = 0;
    1743         174 :     uint32_t n = textlen - patlen + 1;
    1744         244 :     while (i < n) {
    1745             :         const TextChar* pos;
    1746             : 
    1747             :         if (sizeof(TextChar) == 1) {
    1748         157 :             MOZ_ASSERT(pat[0] <= 0xff);
    1749         157 :             pos = (TextChar*) FirstCharMatcher8bit((char*) text + i, n - i, pat[0]);
    1750             :         } else {
    1751          50 :             pos = FirstCharMatcherUnrolled(text + i, n - i, char16_t(pat[0]));
    1752             :         }
    1753             : 
    1754         207 :         if (pos == nullptr)
    1755         138 :             return -1;
    1756             : 
    1757          69 :         i = static_cast<uint32_t>(pos - text);
    1758          69 :         if (InnerMatch::match(pat + 1, text + i + 1, extent))
    1759          34 :             return i;
    1760             : 
    1761          35 :         i += 1;
    1762             :      }
    1763           2 :      return -1;
    1764             :  }
    1765             : 
    1766             : 
    1767             : template <typename TextChar, typename PatChar>
    1768             : static MOZ_ALWAYS_INLINE int
    1769         350 : StringMatch(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t patLen)
    1770             : {
    1771         350 :     if (patLen == 0)
    1772           0 :         return 0;
    1773         350 :     if (textLen < patLen)
    1774         176 :         return -1;
    1775             : 
    1776             : #if defined(__i386__) || defined(_M_IX86) || defined(__i386)
    1777             :     /*
    1778             :      * Given enough registers, the unrolled loop below is faster than the
    1779             :      * following loop. 32-bit x86 does not have enough registers.
    1780             :      */
    1781             :     if (patLen == 1) {
    1782             :         const PatChar p0 = *pat;
    1783             :         const TextChar* end = text + textLen;
    1784             :         for (const TextChar* c = text; c != end; ++c) {
    1785             :             if (*c == p0)
    1786             :                 return c - text;
    1787             :         }
    1788             :         return -1;
    1789             :     }
    1790             : #endif
    1791             : 
    1792             :     /*
    1793             :      * If the text or pattern string is short, BMH will be more expensive than
    1794             :      * the basic linear scan due to initialization cost and a more complex loop
    1795             :      * body. While the correct threshold is input-dependent, we can make a few
    1796             :      * conservative observations:
    1797             :      *  - When |textLen| is "big enough", the initialization time will be
    1798             :      *    proportionally small, so the worst-case slowdown is minimized.
    1799             :      *  - When |patLen| is "too small", even the best case for BMH will be
    1800             :      *    slower than a simple scan for large |textLen| due to the more complex
    1801             :      *    loop body of BMH.
    1802             :      * From this, the values for "big enough" and "too small" are determined
    1803             :      * empirically. See bug 526348.
    1804             :      */
    1805         174 :     if (textLen >= 512 && patLen >= 11 && patLen <= sBMHPatLenMax) {
    1806           0 :         int index = BoyerMooreHorspool(text, textLen, pat, patLen);
    1807           0 :         if (index != sBMHBadPattern)
    1808           0 :             return index;
    1809             :     }
    1810             : 
    1811             :     /*
    1812             :      * For big patterns with large potential overlap we want the SIMD-optimized
    1813             :      * speed of memcmp. For small patterns, a simple loop is faster. We also can't
    1814             :      * use memcmp if one of the strings is TwoByte and the other is Latin-1.
    1815             :      *
    1816             :      * FIXME: Linux memcmp performance is sad and the manual loop is faster.
    1817             :      */
    1818             :     return
    1819             : #if !defined(__linux__)
    1820             :         (patLen > 128 && IsSame<TextChar, PatChar>::value)
    1821             :             ? Matcher<MemCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen)
    1822             :             :
    1823             : #endif
    1824         174 :               Matcher<ManualCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen);
    1825             : }
    1826             : 
    1827             : static int32_t
    1828         350 : StringMatch(JSLinearString* text, JSLinearString* pat, uint32_t start = 0)
    1829             : {
    1830         350 :     MOZ_ASSERT(start <= text->length());
    1831         350 :     uint32_t textLen = text->length() - start;
    1832         350 :     uint32_t patLen = pat->length();
    1833             : 
    1834             :     int match;
    1835         700 :     AutoCheckCannotGC nogc;
    1836         350 :     if (text->hasLatin1Chars()) {
    1837         322 :         const Latin1Char* textChars = text->latin1Chars(nogc) + start;
    1838         322 :         if (pat->hasLatin1Chars())
    1839         322 :             match = StringMatch(textChars, textLen, pat->latin1Chars(nogc), patLen);
    1840             :         else
    1841           0 :             match = StringMatch(textChars, textLen, pat->twoByteChars(nogc), patLen);
    1842             :     } else {
    1843          28 :         const char16_t* textChars = text->twoByteChars(nogc) + start;
    1844          28 :         if (pat->hasLatin1Chars())
    1845          28 :             match = StringMatch(textChars, textLen, pat->latin1Chars(nogc), patLen);
    1846             :         else
    1847           0 :             match = StringMatch(textChars, textLen, pat->twoByteChars(nogc), patLen);
    1848             :     }
    1849             : 
    1850         700 :     return (match == -1) ? -1 : start + match;
    1851             : }
    1852             : 
    1853             : static const size_t sRopeMatchThresholdRatioLog2 = 4;
    1854             : 
    1855             : bool
    1856           0 : js::StringHasPattern(JSLinearString* text, const char16_t* pat, uint32_t patLen)
    1857             : {
    1858           0 :     AutoCheckCannotGC nogc;
    1859           0 :     return text->hasLatin1Chars()
    1860           0 :            ? StringMatch(text->latin1Chars(nogc), text->length(), pat, patLen) != -1
    1861           0 :            : StringMatch(text->twoByteChars(nogc), text->length(), pat, patLen) != -1;
    1862             : }
    1863             : 
    1864             : int
    1865          19 : js::StringFindPattern(JSLinearString* text, JSLinearString* pat, size_t start)
    1866             : {
    1867          19 :     return StringMatch(text, pat, start);
    1868             : }
    1869             : 
    1870             : // When an algorithm does not need a string represented as a single linear
    1871             : // array of characters, this range utility may be used to traverse the string a
    1872             : // sequence of linear arrays of characters. This avoids flattening ropes.
    1873           0 : class StringSegmentRange
    1874             : {
    1875             :     // If malloc() shows up in any profiles from this vector, we can add a new
    1876             :     // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx.
    1877             :     using StackVector = JS::GCVector<JSString*, 16>;
    1878             :     Rooted<StackVector> stack;
    1879             :     RootedLinearString cur;
    1880             : 
    1881           0 :     bool settle(JSString* str) {
    1882           0 :         while (str->isRope()) {
    1883           0 :             JSRope& rope = str->asRope();
    1884           0 :             if (!stack.append(rope.rightChild()))
    1885           0 :                 return false;
    1886           0 :             str = rope.leftChild();
    1887             :         }
    1888           0 :         cur = &str->asLinear();
    1889           0 :         return true;
    1890             :     }
    1891             : 
    1892             :   public:
    1893           0 :     explicit StringSegmentRange(JSContext* cx)
    1894           0 :       : stack(cx, StackVector(cx)), cur(cx)
    1895           0 :     {}
    1896             : 
    1897           0 :     MOZ_MUST_USE bool init(JSString* str) {
    1898           0 :         MOZ_ASSERT(stack.empty());
    1899           0 :         return settle(str);
    1900             :     }
    1901             : 
    1902           0 :     bool empty() const {
    1903           0 :         return cur == nullptr;
    1904             :     }
    1905             : 
    1906           0 :     JSLinearString* front() const {
    1907           0 :         MOZ_ASSERT(!cur->isRope());
    1908           0 :         return cur;
    1909             :     }
    1910             : 
    1911           0 :     MOZ_MUST_USE bool popFront() {
    1912           0 :         MOZ_ASSERT(!empty());
    1913           0 :         if (stack.empty()) {
    1914           0 :             cur = nullptr;
    1915           0 :             return true;
    1916             :         }
    1917           0 :         return settle(stack.popCopy());
    1918             :     }
    1919             : };
    1920             : 
    1921             : typedef Vector<JSLinearString*, 16, SystemAllocPolicy> LinearStringVector;
    1922             : 
    1923             : template <typename TextChar, typename PatChar>
    1924             : static int
    1925           0 : RopeMatchImpl(const AutoCheckCannotGC& nogc, LinearStringVector& strings,
    1926             :               const PatChar* pat, size_t patLen)
    1927             : {
    1928             :     /* Absolute offset from the beginning of the logical text string. */
    1929           0 :     int pos = 0;
    1930             : 
    1931           0 :     for (JSLinearString** outerp = strings.begin(); outerp != strings.end(); ++outerp) {
    1932             :         /* Try to find a match within 'outer'. */
    1933           0 :         JSLinearString* outer = *outerp;
    1934           0 :         const TextChar* chars = outer->chars<TextChar>(nogc);
    1935           0 :         size_t len = outer->length();
    1936           0 :         int matchResult = StringMatch(chars, len, pat, patLen);
    1937           0 :         if (matchResult != -1) {
    1938             :             /* Matched! */
    1939           0 :             return pos + matchResult;
    1940             :         }
    1941             : 
    1942             :         /* Try to find a match starting in 'outer' and running into other nodes. */
    1943           0 :         const TextChar* const text = chars + (patLen > len ? 0 : len - patLen + 1);
    1944           0 :         const TextChar* const textend = chars + len;
    1945           0 :         const PatChar p0 = *pat;
    1946           0 :         const PatChar* const p1 = pat + 1;
    1947           0 :         const PatChar* const patend = pat + patLen;
    1948           0 :         for (const TextChar* t = text; t != textend; ) {
    1949           0 :             if (*t++ != p0)
    1950           0 :                 continue;
    1951             : 
    1952           0 :             JSLinearString** innerp = outerp;
    1953           0 :             const TextChar* ttend = textend;
    1954           0 :             const TextChar* tt = t;
    1955           0 :             for (const PatChar* pp = p1; pp != patend; ++pp, ++tt) {
    1956           0 :                 while (tt == ttend) {
    1957           0 :                     if (++innerp == strings.end())
    1958           0 :                         return -1;
    1959             : 
    1960           0 :                     JSLinearString* inner = *innerp;
    1961           0 :                     tt = inner->chars<TextChar>(nogc);
    1962           0 :                     ttend = tt + inner->length();
    1963             :                 }
    1964           0 :                 if (*pp != *tt)
    1965           0 :                     goto break_continue;
    1966             :             }
    1967             : 
    1968             :             /* Matched! */
    1969           0 :             return pos + (t - chars) - 1;  /* -1 because of *t++ above */
    1970             : 
    1971             :           break_continue:;
    1972             :         }
    1973             : 
    1974           0 :         pos += len;
    1975             :     }
    1976             : 
    1977           0 :     return -1;
    1978             : }
    1979             : 
    1980             : /*
    1981             :  * RopeMatch takes the text to search and the pattern to search for in the text.
    1982             :  * RopeMatch returns false on OOM and otherwise returns the match index through
    1983             :  * the 'match' outparam (-1 for not found).
    1984             :  */
    1985             : static bool
    1986           0 : RopeMatch(JSContext* cx, JSRope* text, JSLinearString* pat, int* match)
    1987             : {
    1988           0 :     uint32_t patLen = pat->length();
    1989           0 :     if (patLen == 0) {
    1990           0 :         *match = 0;
    1991           0 :         return true;
    1992             :     }
    1993           0 :     if (text->length() < patLen) {
    1994           0 :         *match = -1;
    1995           0 :         return true;
    1996             :     }
    1997             : 
    1998             :     /*
    1999             :      * List of leaf nodes in the rope. If we run out of memory when trying to
    2000             :      * append to this list, we can still fall back to StringMatch, so use the
    2001             :      * system allocator so we don't report OOM in that case.
    2002             :      */
    2003           0 :     LinearStringVector strings;
    2004             : 
    2005             :     /*
    2006             :      * We don't want to do rope matching if there is a poor node-to-char ratio,
    2007             :      * since this means spending a lot of time in the match loop below. We also
    2008             :      * need to build the list of leaf nodes. Do both here: iterate over the
    2009             :      * nodes so long as there are not too many.
    2010             :      *
    2011             :      * We also don't use rope matching if the rope contains both Latin-1 and
    2012             :      * TwoByte nodes, to simplify the match algorithm.
    2013             :      */
    2014             :     {
    2015           0 :         size_t threshold = text->length() >> sRopeMatchThresholdRatioLog2;
    2016           0 :         StringSegmentRange r(cx);
    2017           0 :         if (!r.init(text))
    2018           0 :             return false;
    2019             : 
    2020           0 :         bool textIsLatin1 = text->hasLatin1Chars();
    2021           0 :         while (!r.empty()) {
    2022           0 :             if (threshold-- == 0 ||
    2023           0 :                 r.front()->hasLatin1Chars() != textIsLatin1 ||
    2024           0 :                 !strings.append(r.front()))
    2025             :             {
    2026           0 :                 JSLinearString* linear = text->ensureLinear(cx);
    2027           0 :                 if (!linear)
    2028           0 :                     return false;
    2029             : 
    2030           0 :                 *match = StringMatch(linear, pat);
    2031           0 :                 return true;
    2032             :             }
    2033           0 :             if (!r.popFront())
    2034           0 :                 return false;
    2035             :         }
    2036             :     }
    2037             : 
    2038           0 :     AutoCheckCannotGC nogc;
    2039           0 :     if (text->hasLatin1Chars()) {
    2040           0 :         if (pat->hasLatin1Chars())
    2041           0 :             *match = RopeMatchImpl<Latin1Char>(nogc, strings, pat->latin1Chars(nogc), patLen);
    2042             :         else
    2043           0 :             *match = RopeMatchImpl<Latin1Char>(nogc, strings, pat->twoByteChars(nogc), patLen);
    2044             :     } else {
    2045           0 :         if (pat->hasLatin1Chars())
    2046           0 :             *match = RopeMatchImpl<char16_t>(nogc, strings, pat->latin1Chars(nogc), patLen);
    2047             :         else
    2048           0 :             *match = RopeMatchImpl<char16_t>(nogc, strings, pat->twoByteChars(nogc), patLen);
    2049             :     }
    2050             : 
    2051           0 :     return true;
    2052             : }
    2053             : 
    2054             : /* ES6 draft rc4 21.1.3.7. */
    2055             : bool
    2056          11 : js::str_includes(JSContext* cx, unsigned argc, Value* vp)
    2057             : {
    2058          11 :     CallArgs args = CallArgsFromVp(argc, vp);
    2059             : 
    2060             :     // Steps 1, 2, and 3
    2061          22 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    2062          11 :     if (!str)
    2063           0 :         return false;
    2064             : 
    2065             :     // Steps 4 and 5
    2066             :     bool isRegExp;
    2067          11 :     if (!IsRegExp(cx, args.get(0), &isRegExp))
    2068           0 :         return false;
    2069             : 
    2070             :     // Step 6
    2071          11 :     if (isRegExp) {
    2072             :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
    2073           0 :                                   "first", "", "Regular Expression");
    2074           0 :         return false;
    2075             :     }
    2076             : 
    2077             :     // Steps 7 and 8
    2078          22 :     RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
    2079          11 :     if (!searchStr)
    2080           0 :         return false;
    2081             : 
    2082             :     // Steps 9 and 10
    2083          11 :     uint32_t pos = 0;
    2084          11 :     if (args.hasDefined(1)) {
    2085           0 :         if (args[1].isInt32()) {
    2086           0 :             int i = args[1].toInt32();
    2087           0 :             pos = (i < 0) ? 0U : uint32_t(i);
    2088             :         } else {
    2089             :             double d;
    2090           0 :             if (!ToInteger(cx, args[1], &d))
    2091           0 :                 return false;
    2092           0 :             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
    2093             :         }
    2094             :     }
    2095             : 
    2096             :     // Step 11
    2097          11 :     uint32_t textLen = str->length();
    2098             : 
    2099             :     // Step 12
    2100          11 :     uint32_t start = Min(Max(pos, 0U), textLen);
    2101             : 
    2102             :     // Steps 13 and 14
    2103          11 :     JSLinearString* text = str->ensureLinear(cx);
    2104          11 :     if (!text)
    2105           0 :         return false;
    2106             : 
    2107          11 :     args.rval().setBoolean(StringMatch(text, searchStr, start) != -1);
    2108          11 :     return true;
    2109             : }
    2110             : 
    2111             : /* ES6 20120927 draft 15.5.4.7. */
    2112             : bool
    2113         282 : js::str_indexOf(JSContext* cx, unsigned argc, Value* vp)
    2114             : {
    2115         282 :     CallArgs args = CallArgsFromVp(argc, vp);
    2116             : 
    2117             :     // Steps 1, 2, and 3
    2118         564 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    2119         282 :     if (!str)
    2120           0 :         return false;
    2121             : 
    2122             :     // Steps 4 and 5
    2123         564 :     RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
    2124         282 :     if (!searchStr)
    2125           0 :         return false;
    2126             : 
    2127             :     // Steps 6 and 7
    2128         282 :     uint32_t pos = 0;
    2129         282 :     if (args.hasDefined(1)) {
    2130           0 :         if (args[1].isInt32()) {
    2131           0 :             int i = args[1].toInt32();
    2132           0 :             pos = (i < 0) ? 0U : uint32_t(i);
    2133             :         } else {
    2134             :             double d;
    2135           0 :             if (!ToInteger(cx, args[1], &d))
    2136           0 :                 return false;
    2137           0 :             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
    2138             :         }
    2139             :     }
    2140             : 
    2141             :    // Step 8
    2142         282 :     uint32_t textLen = str->length();
    2143             : 
    2144             :     // Step 9
    2145         282 :     uint32_t start = Min(Max(pos, 0U), textLen);
    2146             : 
    2147             :     // Steps 10 and 11
    2148         282 :     JSLinearString* text = str->ensureLinear(cx);
    2149         282 :     if (!text)
    2150           0 :         return false;
    2151             : 
    2152         282 :     args.rval().setInt32(StringMatch(text, searchStr, start));
    2153         282 :     return true;
    2154             : }
    2155             : 
    2156             : template <typename TextChar, typename PatChar>
    2157             : static int32_t
    2158          13 : LastIndexOfImpl(const TextChar* text, size_t textLen, const PatChar* pat, size_t patLen,
    2159             :                 size_t start)
    2160             : {
    2161          13 :     MOZ_ASSERT(patLen > 0);
    2162          13 :     MOZ_ASSERT(patLen <= textLen);
    2163          13 :     MOZ_ASSERT(start <= textLen - patLen);
    2164             : 
    2165          13 :     const PatChar p0 = *pat;
    2166          13 :     const PatChar* patNext = pat + 1;
    2167          13 :     const PatChar* patEnd = pat + patLen;
    2168             : 
    2169         175 :     for (const TextChar* t = text + start; t >= text; --t) {
    2170         175 :         if (*t == p0) {
    2171          13 :             const TextChar* t1 = t + 1;
    2172          13 :             for (const PatChar* p1 = patNext; p1 < patEnd; ++p1, ++t1) {
    2173           0 :                 if (*t1 != *p1)
    2174           0 :                     goto break_continue;
    2175             :             }
    2176             : 
    2177          13 :             return static_cast<int32_t>(t - text);
    2178             :         }
    2179             :       break_continue:;
    2180             :     }
    2181             : 
    2182           0 :     return -1;
    2183             : }
    2184             : 
    2185             : // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
    2186             : // 21.1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] )
    2187             : bool
    2188          13 : js::str_lastIndexOf(JSContext* cx, unsigned argc, Value* vp)
    2189             : {
    2190          13 :     CallArgs args = CallArgsFromVp(argc, vp);
    2191             : 
    2192             :     // Steps 1-2.
    2193          26 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    2194          13 :     if (!str)
    2195           0 :         return false;
    2196             : 
    2197             :     // Step 3.
    2198          26 :     RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
    2199          13 :     if (!searchStr)
    2200           0 :         return false;
    2201             : 
    2202             :     // Step 6.
    2203          13 :     size_t len = str->length();
    2204             : 
    2205             :     // Step 8.
    2206          13 :     size_t searchLen = searchStr->length();
    2207             : 
    2208             :     // Steps 4-5, 7.
    2209          13 :     int start = len - searchLen; // Start searching here
    2210          13 :     if (args.hasDefined(1)) {
    2211           0 :         if (args[1].isInt32()) {
    2212           0 :             int i = args[1].toInt32();
    2213           0 :             if (i <= 0)
    2214           0 :                 start = 0;
    2215           0 :             else if (i < start)
    2216           0 :                 start = i;
    2217             :         } else {
    2218             :             double d;
    2219           0 :             if (!ToNumber(cx, args[1], &d))
    2220           0 :                 return false;
    2221           0 :             if (!IsNaN(d)) {
    2222           0 :                 d = JS::ToInteger(d);
    2223           0 :                 if (d <= 0)
    2224           0 :                     start = 0;
    2225           0 :                 else if (d < start)
    2226           0 :                     start = int(d);
    2227             :             }
    2228             :         }
    2229             :     }
    2230             : 
    2231          13 :     if (searchLen > len) {
    2232           0 :         args.rval().setInt32(-1);
    2233           0 :         return true;
    2234             :     }
    2235             : 
    2236          13 :     if (searchLen == 0) {
    2237           0 :         args.rval().setInt32(start);
    2238           0 :         return true;
    2239             :     }
    2240          13 :     MOZ_ASSERT(0 <= start && size_t(start) < len);
    2241             : 
    2242          13 :     JSLinearString* text = str->ensureLinear(cx);
    2243          13 :     if (!text)
    2244           0 :         return false;
    2245             : 
    2246             :     // Step 9.
    2247             :     int32_t res;
    2248          26 :     AutoCheckCannotGC nogc;
    2249          13 :     if (text->hasLatin1Chars()) {
    2250          12 :         const Latin1Char* textChars = text->latin1Chars(nogc);
    2251          12 :         if (searchStr->hasLatin1Chars())
    2252          12 :             res = LastIndexOfImpl(textChars, len, searchStr->latin1Chars(nogc), searchLen, start);
    2253             :         else
    2254           0 :             res = LastIndexOfImpl(textChars, len, searchStr->twoByteChars(nogc), searchLen, start);
    2255             :     } else {
    2256           1 :         const char16_t* textChars = text->twoByteChars(nogc);
    2257           1 :         if (searchStr->hasLatin1Chars())
    2258           1 :             res = LastIndexOfImpl(textChars, len, searchStr->latin1Chars(nogc), searchLen, start);
    2259             :         else
    2260           0 :             res = LastIndexOfImpl(textChars, len, searchStr->twoByteChars(nogc), searchLen, start);
    2261             :     }
    2262             : 
    2263          13 :     args.rval().setInt32(res);
    2264          13 :     return true;
    2265             : }
    2266             : 
    2267             : bool
    2268         481 : js::HasSubstringAt(JSLinearString* text, JSLinearString* pat, size_t start)
    2269             : {
    2270         481 :     MOZ_ASSERT(start + pat->length() <= text->length());
    2271             : 
    2272         481 :     size_t patLen = pat->length();
    2273             : 
    2274         962 :     AutoCheckCannotGC nogc;
    2275         481 :     if (text->hasLatin1Chars()) {
    2276         478 :         const Latin1Char* textChars = text->latin1Chars(nogc) + start;
    2277         478 :         if (pat->hasLatin1Chars())
    2278         478 :             return PodEqual(textChars, pat->latin1Chars(nogc), patLen);
    2279             : 
    2280           0 :         return EqualChars(textChars, pat->twoByteChars(nogc), patLen);
    2281             :     }
    2282             : 
    2283           3 :     const char16_t* textChars = text->twoByteChars(nogc) + start;
    2284           3 :     if (pat->hasTwoByteChars())
    2285           0 :         return PodEqual(textChars, pat->twoByteChars(nogc), patLen);
    2286             : 
    2287           3 :     return EqualChars(pat->latin1Chars(nogc), textChars, patLen);
    2288             : }
    2289             : 
    2290             : /* ES6 draft rc3 21.1.3.18. */
    2291             : bool
    2292         378 : js::str_startsWith(JSContext* cx, unsigned argc, Value* vp)
    2293             : {
    2294         378 :     CallArgs args = CallArgsFromVp(argc, vp);
    2295             : 
    2296             :     // Steps 1, 2, and 3
    2297         756 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    2298         378 :     if (!str)
    2299           0 :         return false;
    2300             : 
    2301             :     // Steps 4 and 5
    2302             :     bool isRegExp;
    2303         378 :     if (!IsRegExp(cx, args.get(0), &isRegExp))
    2304           0 :         return false;
    2305             : 
    2306             :     // Step 6
    2307         378 :     if (isRegExp) {
    2308             :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
    2309           0 :                                   "first", "", "Regular Expression");
    2310           0 :         return false;
    2311             :     }
    2312             : 
    2313             :     // Steps 7 and 8
    2314         756 :     RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
    2315         378 :     if (!searchStr)
    2316           0 :         return false;
    2317             : 
    2318             :     // Steps 9 and 10
    2319         378 :     uint32_t pos = 0;
    2320         378 :     if (args.hasDefined(1)) {
    2321           0 :         if (args[1].isInt32()) {
    2322           0 :             int i = args[1].toInt32();
    2323           0 :             pos = (i < 0) ? 0U : uint32_t(i);
    2324             :         } else {
    2325             :             double d;
    2326           0 :             if (!ToInteger(cx, args[1], &d))
    2327           0 :                 return false;
    2328           0 :             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
    2329             :         }
    2330             :     }
    2331             : 
    2332             :     // Step 11
    2333         378 :     uint32_t textLen = str->length();
    2334             : 
    2335             :     // Step 12
    2336         378 :     uint32_t start = Min(Max(pos, 0U), textLen);
    2337             : 
    2338             :     // Step 13
    2339         378 :     uint32_t searchLen = searchStr->length();
    2340             : 
    2341             :     // Step 14
    2342         378 :     if (searchLen + start < searchLen || searchLen + start > textLen) {
    2343          68 :         args.rval().setBoolean(false);
    2344          68 :         return true;
    2345             :     }
    2346             : 
    2347             :     // Steps 15 and 16
    2348         310 :     JSLinearString* text = str->ensureLinear(cx);
    2349         310 :     if (!text)
    2350           0 :         return false;
    2351             : 
    2352         310 :     args.rval().setBoolean(HasSubstringAt(text, searchStr, start));
    2353         310 :     return true;
    2354             : }
    2355             : 
    2356             : /* ES6 draft rc3 21.1.3.6. */
    2357             : bool
    2358         179 : js::str_endsWith(JSContext* cx, unsigned argc, Value* vp)
    2359             : {
    2360         179 :     CallArgs args = CallArgsFromVp(argc, vp);
    2361             : 
    2362             :     // Steps 1, 2, and 3
    2363         358 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    2364         179 :     if (!str)
    2365           0 :         return false;
    2366             : 
    2367             :     // Steps 4 and 5
    2368             :     bool isRegExp;
    2369         179 :     if (!IsRegExp(cx, args.get(0), &isRegExp))
    2370           0 :         return false;
    2371             : 
    2372             :     // Step 6
    2373         179 :     if (isRegExp) {
    2374             :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
    2375           0 :                                   "first", "", "Regular Expression");
    2376           0 :         return false;
    2377             :     }
    2378             : 
    2379             :     // Steps 7 and 8
    2380         358 :     RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
    2381         179 :     if (!searchStr)
    2382           0 :         return false;
    2383             : 
    2384             :     // Step 9
    2385         179 :     uint32_t textLen = str->length();
    2386             : 
    2387             :     // Steps 10 and 11
    2388         179 :     uint32_t pos = textLen;
    2389         179 :     if (args.hasDefined(1)) {
    2390           0 :         if (args[1].isInt32()) {
    2391           0 :             int i = args[1].toInt32();
    2392           0 :             pos = (i < 0) ? 0U : uint32_t(i);
    2393             :         } else {
    2394             :             double d;
    2395           0 :             if (!ToInteger(cx, args[1], &d))
    2396           0 :                 return false;
    2397           0 :             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
    2398             :         }
    2399             :     }
    2400             : 
    2401             :     // Step 12
    2402         179 :     uint32_t end = Min(Max(pos, 0U), textLen);
    2403             : 
    2404             :     // Step 13
    2405         179 :     uint32_t searchLen = searchStr->length();
    2406             : 
    2407             :     // Step 15 (reordered)
    2408         179 :     if (searchLen > end) {
    2409           8 :         args.rval().setBoolean(false);
    2410           8 :         return true;
    2411             :     }
    2412             : 
    2413             :     // Step 14
    2414         171 :     uint32_t start = end - searchLen;
    2415             : 
    2416             :     // Steps 16 and 17
    2417         171 :     JSLinearString* text = str->ensureLinear(cx);
    2418         171 :     if (!text)
    2419           0 :         return false;
    2420             : 
    2421         171 :     args.rval().setBoolean(HasSubstringAt(text, searchStr, start));
    2422         171 :     return true;
    2423             : }
    2424             : 
    2425             : template <typename CharT>
    2426             : static void
    2427          48 : TrimString(const CharT* chars, bool trimLeft, bool trimRight, size_t length,
    2428             :            size_t* pBegin, size_t* pEnd)
    2429             : {
    2430          48 :     size_t begin = 0, end = length;
    2431             : 
    2432          48 :     if (trimLeft) {
    2433          48 :         while (begin < length && unicode::IsSpace(chars[begin]))
    2434           0 :             ++begin;
    2435             :     }
    2436             : 
    2437          48 :     if (trimRight) {
    2438          48 :         while (end > begin && unicode::IsSpace(chars[end - 1]))
    2439           0 :             --end;
    2440             :     }
    2441             : 
    2442          48 :     *pBegin = begin;
    2443          48 :     *pEnd = end;
    2444          48 : }
    2445             : 
    2446             : static bool
    2447          48 : TrimString(JSContext* cx, const CallArgs& args, bool trimLeft, bool trimRight)
    2448             : {
    2449          96 :     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
    2450          48 :     if (!str)
    2451           0 :         return false;
    2452             : 
    2453          48 :     JSLinearString* linear = str->ensureLinear(cx);
    2454          48 :     if (!linear)
    2455           0 :         return false;
    2456             : 
    2457          48 :     size_t length = linear->length();
    2458             :     size_t begin, end;
    2459          48 :     if (linear->hasLatin1Chars()) {
    2460          90 :         AutoCheckCannotGC nogc;
    2461          45 :         TrimString(linear->latin1Chars(nogc), trimLeft, trimRight, length, &begin, &end);
    2462             :     } else {
    2463           6 :         AutoCheckCannotGC nogc;
    2464           3 :         TrimString(linear->twoByteChars(nogc), trimLeft, trimRight, length, &begin, &end);
    2465             :     }
    2466             : 
    2467          48 :     str = NewDependentString(cx, str, begin, end - begin);
    2468          48 :     if (!str)
    2469           0 :         return false;
    2470             : 
    2471          48 :     args.rval().setString(str);
    2472          48 :     return true;
    2473             : }
    2474             : 
    2475             : bool
    2476          48 : js::str_trim(JSContext* cx, unsigned argc, Value* vp)
    2477             : {
    2478          48 :     CallArgs args = CallArgsFromVp(argc, vp);
    2479          48 :     return TrimString(cx, args, true, true);
    2480             : }
    2481             : 
    2482             : bool
    2483           0 : js::str_trimLeft(JSContext* cx, unsigned argc, Value* vp)
    2484             : {
    2485           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    2486           0 :     return TrimString(cx, args, true, false);
    2487             : }
    2488             : 
    2489             : bool
    2490           0 : js::str_trimRight(JSContext* cx, unsigned argc, Value* vp)
    2491             : {
    2492           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    2493           0 :     return TrimString(cx, args, false, true);
    2494             : }
    2495             : 
    2496             : // Utility for building a rope (lazy concatenation) of strings.
    2497          14 : class RopeBuilder {
    2498             :     JSContext* cx;
    2499             :     RootedString res;
    2500             : 
    2501             :     RopeBuilder(const RopeBuilder& other) = delete;
    2502             :     void operator=(const RopeBuilder& other) = delete;
    2503             : 
    2504             :   public:
    2505          14 :     explicit RopeBuilder(JSContext* cx)
    2506          14 :       : cx(cx), res(cx, cx->runtime()->emptyString)
    2507          14 :     {}
    2508             : 
    2509          42 :     inline bool append(HandleString str) {
    2510          42 :         res = ConcatStrings<CanGC>(cx, res, str);
    2511          42 :         return !!res;
    2512             :     }
    2513             : 
    2514          14 :     inline JSString* result() {
    2515          14 :         return res;
    2516             :     }
    2517             : };
    2518             : 
    2519             : namespace {
    2520             : 
    2521             : template <typename CharT>
    2522             : static uint32_t
    2523          28 : FindDollarIndex(const CharT* chars, size_t length)
    2524             : {
    2525          28 :     if (const CharT* p = js_strchr_limit(chars, '$', chars + length)) {
    2526           0 :         uint32_t dollarIndex = p - chars;
    2527           0 :         MOZ_ASSERT(dollarIndex < length);
    2528           0 :         return dollarIndex;
    2529             :     }
    2530          28 :     return UINT32_MAX;
    2531             : }
    2532             : 
    2533             : } /* anonymous namespace */
    2534             : 
    2535             : static JSString*
    2536          14 : BuildFlatReplacement(JSContext* cx, HandleString textstr, HandleString repstr,
    2537             :                      size_t match, size_t patternLength)
    2538             : {
    2539          28 :     RopeBuilder builder(cx);
    2540          14 :     size_t matchEnd = match + patternLength;
    2541             : 
    2542          14 :     if (textstr->isRope()) {
    2543             :         /*
    2544             :          * If we are replacing over a rope, avoid flattening it by iterating
    2545             :          * through it, building a new rope.
    2546             :          */
    2547           0 :         StringSegmentRange r(cx);
    2548           0 :         if (!r.init(textstr))
    2549           0 :             return nullptr;
    2550             : 
    2551           0 :         size_t pos = 0;
    2552           0 :         while (!r.empty()) {
    2553           0 :             RootedString str(cx, r.front());
    2554           0 :             size_t len = str->length();
    2555           0 :             size_t strEnd = pos + len;
    2556           0 :             if (pos < matchEnd && strEnd > match) {
    2557             :                 /*
    2558             :                  * We need to special-case any part of the rope that overlaps
    2559             :                  * with the replacement string.
    2560             :                  */
    2561           0 :                 if (match >= pos) {
    2562             :                     /*
    2563             :                      * If this part of the rope overlaps with the left side of
    2564             :                      * the pattern, then it must be the only one to overlap with
    2565             :                      * the first character in the pattern, so we include the
    2566             :                      * replacement string here.
    2567             :                      */
    2568           0 :                     RootedString leftSide(cx, NewDependentString(cx, str, 0, match - pos));
    2569           0 :                     if (!leftSide ||
    2570           0 :                         !builder.append(leftSide) ||
    2571           0 :                         !builder.append(repstr))
    2572             :                     {
    2573           0 :                         return nullptr;
    2574             :                     }
    2575             :                 }
    2576             : 
    2577             :                 /*
    2578             :                  * If str runs off the end of the matched string, append the
    2579             :                  * last part of str.
    2580             :                  */
    2581           0 :                 if (strEnd > matchEnd) {
    2582           0 :                     RootedString rightSide(cx, NewDependentString(cx, str, matchEnd - pos,
    2583           0 :                                                                   strEnd - matchEnd));
    2584           0 :                     if (!rightSide || !builder.append(rightSide))
    2585           0 :                         return nullptr;
    2586           0 :                 }
    2587             :             } else {
    2588           0 :                 if (!builder.append(str))
    2589           0 :                     return nullptr;
    2590             :             }
    2591           0 :             pos += str->length();
    2592           0 :             if (!r.popFront())
    2593           0 :                 return nullptr;
    2594             :         }
    2595             :     } else {
    2596          28 :         RootedString leftSide(cx, NewDependentString(cx, textstr, 0, match));
    2597          14 :         if (!leftSide)
    2598           0 :             return nullptr;
    2599          28 :         RootedString rightSide(cx);
    2600          42 :         rightSide = NewDependentString(cx, textstr, match + patternLength,
    2601          42 :                                        textstr->length() - match - patternLength);
    2602          70 :         if (!rightSide ||
    2603          70 :             !builder.append(leftSide) ||
    2604          70 :             !builder.append(repstr) ||
    2605          42 :             !builder.append(rightSide))
    2606             :         {
    2607           0 :             return nullptr;
    2608             :         }
    2609             :     }
    2610             : 
    2611          14 :     return builder.result();
    2612             : }
    2613             : 
    2614             : template <typename CharT>
    2615             : static bool
    2616           0 : AppendDollarReplacement(StringBuffer& newReplaceChars, size_t firstDollarIndex,
    2617             :                         size_t matchStart, size_t matchLimit, JSLinearString* text,
    2618             :                         const CharT* repChars, size_t repLength)
    2619             : {
    2620           0 :     MOZ_ASSERT(firstDollarIndex < repLength);
    2621             : 
    2622             :     /* Move the pre-dollar chunk in bulk. */
    2623           0 :     newReplaceChars.infallibleAppend(repChars, firstDollarIndex);
    2624             : 
    2625             :     /* Move the rest char-by-char, interpreting dollars as we encounter them. */
    2626           0 :     const CharT* repLimit = repChars + repLength;
    2627           0 :     for (const CharT* it = repChars + firstDollarIndex; it < repLimit; ++it) {
    2628           0 :         if (*it != '$' || it == repLimit - 1) {
    2629           0 :             if (!newReplaceChars.append(*it))
    2630           0 :                 return false;
    2631           0 :             continue;
    2632             :         }
    2633             : 
    2634           0 :         switch (*(it + 1)) {
    2635             :           case '$': /* Eat one of the dollars. */
    2636           0 :             if (!newReplaceChars.append(*it))
    2637           0 :                 return false;
    2638           0 :             break;
    2639             :           case '&':
    2640           0 :             if (!newReplaceChars.appendSubstring(text, matchStart, matchLimit - matchStart))
    2641           0 :                 return false;
    2642           0 :             break;
    2643             :           case '`':
    2644           0 :             if (!newReplaceChars.appendSubstring(text, 0, matchStart))
    2645           0 :                 return false;
    2646           0 :             break;
    2647             :           case '\'':
    2648           0 :             if (!newReplaceChars.appendSubstring(text, matchLimit, text->length() - matchLimit))
    2649           0 :                 return false;
    2650           0 :             break;
    2651             :           default: /* The dollar we saw was not special (no matter what its mother told it). */
    2652           0 :             if (!newReplaceChars.append(*it))
    2653           0 :                 return false;
    2654           0 :             continue;
    2655             :         }
    2656           0 :         ++it; /* We always eat an extra char in the above switch. */
    2657             :     }
    2658             : 
    2659           0 :     return true;
    2660             : }
    2661             : 
    2662             : /*
    2663             :  * Perform a linear-scan dollar substitution on the replacement text,
    2664             :  * constructing a result string that looks like:
    2665             :  *
    2666             :  *      newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
    2667             :  */
    2668             : static JSString*
    2669           0 : BuildDollarReplacement(JSContext* cx, JSString* textstrArg, JSLinearString* repstr,
    2670             :                        uint32_t firstDollarIndex, size_t matchStart, size_t patternLength)
    2671             : {
    2672           0 :     RootedLinearString textstr(cx, textstrArg->ensureLinear(cx));
    2673           0 :     if (!textstr)
    2674           0 :         return nullptr;
    2675             : 
    2676           0 :     size_t matchLimit = matchStart + patternLength;
    2677             : 
    2678             :     /*
    2679             :      * Most probably:
    2680             :      *
    2681             :      *      len(newstr) >= len(orig) - len(match) + len(replacement)
    2682             :      *
    2683             :      * Note that dollar vars _could_ make the resulting text smaller than this.
    2684             :      */
    2685           0 :     StringBuffer newReplaceChars(cx);
    2686           0 :     if (repstr->hasTwoByteChars() && !newReplaceChars.ensureTwoByteChars())
    2687           0 :         return nullptr;
    2688             : 
    2689           0 :     if (!newReplaceChars.reserve(textstr->length() - patternLength + repstr->length()))
    2690           0 :         return nullptr;
    2691             : 
    2692             :     bool res;
    2693           0 :     if (repstr->hasLatin1Chars()) {
    2694           0 :         AutoCheckCannotGC nogc;
    2695           0 :         res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, matchStart, matchLimit,
    2696           0 :                                       textstr, repstr->latin1Chars(nogc), repstr->length());
    2697             :     } else {
    2698           0 :         AutoCheckCannotGC nogc;
    2699           0 :         res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, matchStart, matchLimit,
    2700           0 :                                       textstr, repstr->twoByteChars(nogc), repstr->length());
    2701             :     }
    2702           0 :     if (!res)
    2703           0 :         return nullptr;
    2704             : 
    2705           0 :     RootedString leftSide(cx, NewDependentString(cx, textstr, 0, matchStart));
    2706           0 :     if (!leftSide)
    2707           0 :         return nullptr;
    2708             : 
    2709           0 :     RootedString newReplace(cx, newReplaceChars.finishString());
    2710           0 :     if (!newReplace)
    2711           0 :         return nullptr;
    2712             : 
    2713           0 :     MOZ_ASSERT(textstr->length() >= matchLimit);
    2714           0 :     RootedString rightSide(cx, NewDependentString(cx, textstr, matchLimit,
    2715           0 :                                                   textstr->length() - matchLimit));
    2716           0 :     if (!rightSide)
    2717           0 :         return nullptr;
    2718             : 
    2719           0 :     RopeBuilder builder(cx);
    2720           0 :     if (!builder.append(leftSide) || !builder.append(newReplace) || !builder.append(rightSide))
    2721           0 :         return nullptr;
    2722             : 
    2723           0 :     return builder.result();
    2724             : }
    2725             : 
    2726             : template <typename StrChar, typename RepChar>
    2727             : static bool
    2728           0 : StrFlatReplaceGlobal(JSContext *cx, JSLinearString *str, JSLinearString *pat, JSLinearString *rep,
    2729             :                      StringBuffer &sb)
    2730             : {
    2731           0 :     MOZ_ASSERT(str->length() > 0);
    2732             : 
    2733           0 :     AutoCheckCannotGC nogc;
    2734           0 :     const StrChar *strChars = str->chars<StrChar>(nogc);
    2735           0 :     const RepChar *repChars = rep->chars<RepChar>(nogc);
    2736             : 
    2737             :     // The pattern is empty, so we interleave the replacement string in-between
    2738             :     // each character.
    2739           0 :     if (!pat->length()) {
    2740           0 :         CheckedInt<uint32_t> strLength(str->length());
    2741           0 :         CheckedInt<uint32_t> repLength(rep->length());
    2742           0 :         CheckedInt<uint32_t> length = repLength * (strLength - 1) + strLength;
    2743           0 :         if (!length.isValid()) {
    2744           0 :             ReportAllocationOverflow(cx);
    2745           0 :             return false;
    2746             :         }
    2747             : 
    2748           0 :         if (!sb.reserve(length.value()))
    2749           0 :             return false;
    2750             : 
    2751           0 :         for (unsigned i = 0; i < str->length() - 1; ++i, ++strChars) {
    2752           0 :             sb.infallibleAppend(*strChars);
    2753           0 :             sb.infallibleAppend(repChars, rep->length());
    2754             :         }
    2755           0 :         sb.infallibleAppend(*strChars);
    2756           0 :         return true;
    2757             :     }
    2758             : 
    2759             :     // If it's true, we are sure that the result's length is, at least, the same
    2760             :     // length as |str->length()|.
    2761           0 :     if (rep->length() >= pat->length()) {
    2762           0 :         if (!sb.reserve(str->length()))
    2763           0 :             return false;
    2764             :     }
    2765             : 
    2766           0 :     uint32_t start = 0;
    2767           0 :     for (;;) {
    2768           0 :         int match = StringMatch(str, pat, start);
    2769           0 :         if (match < 0)
    2770           0 :             break;
    2771           0 :         if (!sb.append(strChars + start, match - start))
    2772           0 :             return false;
    2773           0 :         if (!sb.append(repChars, rep->length()))
    2774           0 :             return false;
    2775           0 :         start = match + pat->length();
    2776             :     }
    2777             : 
    2778           0 :     if (!sb.append(strChars + start, str->length() - start))
    2779           0 :         return false;
    2780             : 
    2781           0 :     return true;
    2782             : }
    2783             : 
    2784             : // This is identical to "str.split(pattern).join(replacement)" except that we
    2785             : // do some deforestation optimization in Ion.
    2786             : JSString *
    2787           0 : js::str_flat_replace_string(JSContext *cx, HandleString string, HandleString pattern,
    2788             :                             HandleString replacement)
    2789             : {
    2790           0 :     MOZ_ASSERT(string);
    2791           0 :     MOZ_ASSERT(pattern);
    2792           0 :     MOZ_ASSERT(replacement);
    2793             : 
    2794           0 :     if (!string->length())
    2795           0 :         return string;
    2796             : 
    2797           0 :     RootedLinearString linearRepl(cx, replacement->ensureLinear(cx));
    2798           0 :     if (!linearRepl)
    2799           0 :         return nullptr;
    2800             : 
    2801           0 :     RootedLinearString linearPat(cx, pattern->ensureLinear(cx));
    2802           0 :     if (!linearPat)
    2803           0 :         return nullptr;
    2804             : 
    2805           0 :     RootedLinearString linearStr(cx, string->ensureLinear(cx));
    2806           0 :     if (!linearStr)
    2807           0 :         return nullptr;
    2808             : 
    2809           0 :     StringBuffer sb(cx);
    2810           0 :     if (linearStr->hasTwoByteChars()) {
    2811           0 :         if (!sb.ensureTwoByteChars())
    2812           0 :             return nullptr;
    2813           0 :         if (linearRepl->hasTwoByteChars()) {
    2814           0 :             if (!StrFlatReplaceGlobal<char16_t, char16_t>(cx, linearStr, linearPat, linearRepl, sb))
    2815           0 :                 return nullptr;
    2816             :         } else {
    2817           0 :             if (!StrFlatReplaceGlobal<char16_t, Latin1Char>(cx, linearStr, linearPat, linearRepl, sb))
    2818           0 :                 return nullptr;
    2819             :         }
    2820             :     } else {
    2821           0 :         if (linearRepl->hasTwoByteChars()) {
    2822           0 :             if (!sb.ensureTwoByteChars())
    2823           0 :                 return nullptr;
    2824           0 :             if (!StrFlatReplaceGlobal<Latin1Char, char16_t>(cx, linearStr, linearPat, linearRepl, sb))
    2825           0 :                 return nullptr;
    2826             :         } else {
    2827           0 :             if (!StrFlatReplaceGlobal<Latin1Char, Latin1Char>(cx, linearStr, linearPat, linearRepl, sb))
    2828           0 :                 return nullptr;
    2829             :         }
    2830             :     }
    2831             : 
    2832           0 :     JSString *str = sb.finishString();
    2833           0 :     if (!str)
    2834           0 :         return nullptr;
    2835             : 
    2836           0 :     return str;
    2837             : }
    2838             : 
    2839             : JSString*
    2840          28 : js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
    2841             :                            HandleString replacement)
    2842             : {
    2843          56 :     RootedLinearString repl(cx, replacement->ensureLinear(cx));
    2844          28 :     if (!repl)
    2845           0 :         return nullptr;
    2846             : 
    2847          56 :     RootedAtom pat(cx, AtomizeString(cx, pattern));
    2848          28 :     if (!pat)
    2849           0 :         return nullptr;
    2850             : 
    2851          28 :     size_t patternLength = pat->length();
    2852             :     int32_t match;
    2853             :     uint32_t dollarIndex;
    2854             : 
    2855             :     {
    2856          56 :         AutoCheckCannotGC nogc;
    2857          56 :         dollarIndex = repl->hasLatin1Chars()
    2858          28 :                       ? FindDollarIndex(repl->latin1Chars(nogc), repl->length())
    2859           0 :                       : FindDollarIndex(repl->twoByteChars(nogc), repl->length());
    2860             :     }
    2861             : 
    2862             :     /*
    2863             :      * |string| could be a rope, so we want to avoid flattening it for as
    2864             :      * long as possible.
    2865             :      */
    2866          28 :     if (string->isRope()) {
    2867           0 :         if (!RopeMatch(cx, &string->asRope(), pat, &match))
    2868           0 :             return nullptr;
    2869             :     } else {
    2870          28 :         match = StringMatch(&string->asLinear(), pat, 0);
    2871             :     }
    2872             : 
    2873          28 :     if (match < 0)
    2874          14 :         return string;
    2875             : 
    2876          14 :     if (dollarIndex != UINT32_MAX)
    2877           0 :         return BuildDollarReplacement(cx, string, repl, dollarIndex, match, patternLength);
    2878          14 :     return BuildFlatReplacement(cx, string, repl, match, patternLength);
    2879             : }
    2880             : 
    2881             : // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
    2882             : static JSObject*
    2883           2 : SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
    2884             :             HandleObjectGroup group)
    2885             : {
    2886           2 :     size_t strLength = str->length();
    2887           2 :     size_t sepLength = sep->length();
    2888           2 :     MOZ_ASSERT(sepLength != 0);
    2889             : 
    2890             :     // Step 12.
    2891           2 :     if (strLength == 0) {
    2892             :         // Step 12.a.
    2893           2 :         int match = StringMatch(str, sep, 0);
    2894             : 
    2895             :         // Step 12.b.
    2896           2 :         if (match != -1)
    2897           0 :             return NewFullyAllocatedArrayTryUseGroup(cx, group, 0);
    2898             : 
    2899             :         // Steps 12.c-e.
    2900           4 :         RootedValue v(cx, StringValue(str));
    2901           2 :         return NewCopiedArrayTryUseGroup(cx, group, v.address(), 1);
    2902             :     }
    2903             : 
    2904             :     // Step 3 (reordered).
    2905           0 :     AutoValueVector splits(cx);
    2906             : 
    2907             :     // Step 8 (reordered).
    2908           0 :     size_t lastEndIndex = 0;
    2909             : 
    2910             :     // Step 13.
    2911           0 :     size_t index = 0;
    2912             : 
    2913             :     // Step 14.
    2914           0 :     while (index != strLength) {
    2915             :         // Step 14.a.
    2916           0 :         int match = StringMatch(str, sep, index);
    2917             : 
    2918             :         // Step 14.b.
    2919             :         //
    2920             :         // Our match algorithm differs from the spec in that it returns the
    2921             :         // next index at which a match happens.  If no match happens we're
    2922             :         // done.
    2923             :         //
    2924             :         // But what if the match is at the end of the string (and the string is
    2925             :         // not empty)?  Per 14.c.i this shouldn't be a match, so we have to
    2926             :         // specially exclude it.  Thus this case should hold:
    2927             :         //
    2928             :         //   var a = "abc".split(/\b/);
    2929             :         //   assertEq(a.length, 1);
    2930             :         //   assertEq(a[0], "abc");
    2931           0 :         if (match == -1)
    2932           0 :             break;
    2933             : 
    2934             :         // Step 14.c.
    2935           0 :         size_t endIndex = match + sepLength;
    2936             : 
    2937             :         // Step 14.c.i.
    2938           0 :         if (endIndex == lastEndIndex) {
    2939           0 :             index++;
    2940           0 :             continue;
    2941             :         }
    2942             : 
    2943             :         // Step 14.c.ii.
    2944           0 :         MOZ_ASSERT(lastEndIndex < endIndex);
    2945           0 :         MOZ_ASSERT(sepLength <= strLength);
    2946           0 :         MOZ_ASSERT(lastEndIndex + sepLength <= endIndex);
    2947             : 
    2948             :         // Step 14.c.ii.1.
    2949           0 :         size_t subLength = size_t(endIndex - sepLength - lastEndIndex);
    2950           0 :         JSString* sub = NewDependentString(cx, str, lastEndIndex, subLength);
    2951             : 
    2952             :         // Steps 14.c.ii.2-4.
    2953           0 :         if (!sub || !splits.append(StringValue(sub)))
    2954           0 :             return nullptr;
    2955             : 
    2956             :         // Step 14.c.ii.5.
    2957           0 :         if (splits.length() == limit)
    2958           0 :             return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
    2959             : 
    2960             :         // Step 14.c.ii.6.
    2961           0 :         index = endIndex;
    2962             : 
    2963             :         // Step 14.c.ii.7.
    2964           0 :         lastEndIndex = index;
    2965             :     }
    2966             : 
    2967             :     // Step 15.
    2968           0 :     JSString* sub = NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex);
    2969             : 
    2970             :     // Steps 16-17.
    2971           0 :     if (!sub || !splits.append(StringValue(sub)))
    2972           0 :         return nullptr;
    2973             : 
    2974             :     // Step 18.
    2975           0 :     return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
    2976             : }
    2977             : 
    2978             : // Fast-path for splitting a string into a character array via split("").
    2979             : static JSObject*
    2980           0 : CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObjectGroup group)
    2981             : {
    2982           0 :     size_t strLength = str->length();
    2983           0 :     if (strLength == 0)
    2984           0 :         return NewFullyAllocatedArrayTryUseGroup(cx, group, 0);
    2985             : 
    2986           0 :     js::StaticStrings& staticStrings = cx->staticStrings();
    2987           0 :     uint32_t resultlen = (limit < strLength ? limit : strLength);
    2988             : 
    2989           0 :     AutoValueVector splits(cx);
    2990           0 :     if (!splits.reserve(resultlen))
    2991           0 :         return nullptr;
    2992             : 
    2993           0 :     for (size_t i = 0; i < resultlen; ++i) {
    2994           0 :         JSString* sub = staticStrings.getUnitStringForElement(cx, str, i);
    2995           0 :         if (!sub)
    2996           0 :             return nullptr;
    2997           0 :         splits.infallibleAppend(StringValue(sub));
    2998             :     }
    2999             : 
    3000           0 :     return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
    3001             : }
    3002             : 
    3003             : template <typename TextChar>
    3004             : static MOZ_ALWAYS_INLINE JSObject*
    3005          95 : SplitSingleCharHelper(JSContext* cx, HandleLinearString str, const TextChar* text,
    3006             :                       uint32_t textLen, char16_t patCh, HandleObjectGroup group)
    3007             : {
    3008             :     // Count the number of occurrences of patCh within text.
    3009          95 :     uint32_t count = 0;
    3010        2814 :     for (size_t index = 0; index < textLen; index++) {
    3011        2719 :         if (static_cast<char16_t>(text[index]) == patCh)
    3012         125 :             count++;
    3013             :     }
    3014             : 
    3015             :     // Handle zero-occurrence case - return input string in an array.
    3016          95 :     if (count == 0) {
    3017          58 :         RootedValue strValue(cx, StringValue(str.get()));
    3018          29 :         return NewCopiedArrayTryUseGroup(cx, group, &strValue.get(), 1);
    3019             :     }
    3020             : 
    3021             :     // Reserve memory for substring values.
    3022         132 :     AutoValueVector splits(cx);
    3023          66 :     if (!splits.reserve(count + 1))
    3024           0 :         return nullptr;
    3025             : 
    3026             :     // Add substrings.
    3027          66 :     size_t lastEndIndex = 0;
    3028        2354 :     for (size_t index = 0; index < textLen; index++) {
    3029        2288 :         if (static_cast<char16_t>(text[index]) == patCh) {
    3030         125 :             size_t subLength = size_t(index - lastEndIndex);
    3031         125 :             JSString* sub = NewDependentString(cx, str, lastEndIndex, subLength);
    3032         125 :             if (!sub || !splits.append(StringValue(sub)))
    3033           0 :                 return nullptr;
    3034         125 :             lastEndIndex = index + 1;
    3035             :         }
    3036             :     }
    3037             : 
    3038             :     // Add substring for tail of string (after last match).
    3039          66 :     JSString* sub = NewDependentString(cx, str, lastEndIndex, textLen - lastEndIndex);
    3040          66 :     if (!sub || !splits.append(StringValue(sub)))
    3041           0 :         return nullptr;
    3042             : 
    3043          66 :     return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
    3044             : }
    3045             : 
    3046             : // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
    3047             : static JSObject*
    3048          95 : SplitSingleCharHelper(JSContext* cx, HandleLinearString str, char16_t ch, HandleObjectGroup group) {
    3049             : 
    3050             :     // Step 12.
    3051          95 :     size_t strLength = str->length();
    3052             : 
    3053         190 :     AutoStableStringChars linearChars(cx);
    3054          95 :     if (!linearChars.init(cx, str))
    3055           0 :         return nullptr;
    3056             : 
    3057          95 :     if (linearChars.isLatin1()) {
    3058          95 :         return SplitSingleCharHelper(cx, str, linearChars.latin1Chars(), strLength, ch, group);
    3059             :     } else {
    3060           0 :         return SplitSingleCharHelper(cx, str, linearChars.twoByteChars(), strLength, ch, group);
    3061             :     }
    3062             : }
    3063             : 
    3064             : // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
    3065             : JSObject*
    3066          97 : js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, uint32_t limit)
    3067             : 
    3068             : {
    3069         194 :     RootedLinearString linearStr(cx, str->ensureLinear(cx));
    3070          97 :     if (!linearStr)
    3071           0 :         return nullptr;
    3072             : 
    3073         194 :     RootedLinearString linearSep(cx, sep->ensureLinear(cx));
    3074          97 :     if (!linearSep)
    3075           0 :         return nullptr;
    3076             : 
    3077          97 :     if (linearSep->length() == 0)
    3078           0 :         return CharSplitHelper(cx, linearStr, limit, group);
    3079             : 
    3080          97 :     if (linearSep->length() == 1 && limit >= static_cast<uint32_t>(INT32_MAX)) {
    3081          95 :         char16_t ch = linearSep->latin1OrTwoByteChar(0);
    3082          95 :         return SplitSingleCharHelper(cx, linearStr, ch, group);
    3083             :     }
    3084             : 
    3085           2 :     return SplitHelper(cx, linearStr, limit, linearSep, group);
    3086             : }
    3087             : 
    3088             : /*
    3089             :  * Python-esque sequence operations.
    3090             :  */
    3091             : bool
    3092           0 : js::str_concat(JSContext* cx, unsigned argc, Value* vp)
    3093             : {
    3094           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    3095           0 :     JSString* str = ToStringForStringFunction(cx, args.thisv());
    3096           0 :     if (!str)
    3097           0 :         return false;
    3098             : 
    3099           0 :     for (unsigned i = 0; i < args.length(); i++) {
    3100           0 :         JSString* argStr = ToString<NoGC>(cx, args[i]);
    3101           0 :         if (!argStr) {
    3102           0 :             RootedString strRoot(cx, str);
    3103           0 :             argStr = ToString<CanGC>(cx, args[i]);
    3104           0 :             if (!argStr)
    3105           0 :                 return false;
    3106           0 :             str = strRoot;
    3107             :         }
    3108             : 
    3109           0 :         JSString* next = ConcatStrings<NoGC>(cx, str, argStr);
    3110           0 :         if (next) {
    3111           0 :             str = next;
    3112             :         } else {
    3113           0 :             RootedString strRoot(cx, str), argStrRoot(cx, argStr);
    3114           0 :             str = ConcatStrings<CanGC>(cx, strRoot, argStrRoot);
    3115           0 :             if (!str)
    3116           0 :                 return false;
    3117             :         }
    3118             :     }
    3119             : 
    3120           0 :     args.rval().setString(str);
    3121           0 :     return true;
    3122             : }
    3123             : 
    3124             : static const JSFunctionSpec string_methods[] = {
    3125             : #if JS_HAS_TOSOURCE
    3126             :     JS_FN(js_toSource_str,     str_toSource,          0,0),
    3127             : #endif
    3128             : 
    3129             :     /* Java-like methods. */
    3130             :     JS_FN(js_toString_str,     str_toString,          0,0),
    3131             :     JS_FN(js_valueOf_str,      str_toString,          0,0),
    3132             :     JS_FN("toLowerCase",       str_toLowerCase,       0,0),
    3133             :     JS_FN("toUpperCase",       str_toUpperCase,       0,0),
    3134             :     JS_INLINABLE_FN("charAt",  str_charAt,            1,0, StringCharAt),
    3135             :     JS_INLINABLE_FN("charCodeAt", str_charCodeAt,     1,0, StringCharCodeAt),
    3136             :     JS_SELF_HOSTED_FN("substring", "String_substring", 2,0),
    3137             :     JS_SELF_HOSTED_FN("padStart", "String_pad_start", 2,0),
    3138             :     JS_SELF_HOSTED_FN("padEnd", "String_pad_end", 2,0),
    3139             :     JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0),
    3140             :     JS_FN("includes",          str_includes,          1,0),
    3141             :     JS_FN("indexOf",           str_indexOf,           1,0),
    3142             :     JS_FN("lastIndexOf",       str_lastIndexOf,       1,0),
    3143             :     JS_FN("startsWith",        str_startsWith,        1,0),
    3144             :     JS_FN("endsWith",          str_endsWith,          1,0),
    3145             :     JS_FN("trim",              str_trim,              0,0),
    3146             :     JS_FN("trimLeft",          str_trimLeft,          0,0),
    3147             :     JS_FN("trimRight",         str_trimRight,         0,0),
    3148             : #if EXPOSE_INTL_API
    3149             :     JS_SELF_HOSTED_FN("toLocaleLowerCase", "String_toLocaleLowerCase", 0,0),
    3150             :     JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
    3151             :     JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
    3152             : #else
    3153             :     JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
    3154             :     JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
    3155             :     JS_FN("localeCompare",     str_localeCompare,     1,0),
    3156             : #endif
    3157             :     JS_SELF_HOSTED_FN("repeat", "String_repeat",      1,0),
    3158             : #if EXPOSE_INTL_API
    3159             :     JS_FN("normalize",         str_normalize,         0,0),
    3160             : #endif
    3161             : 
    3162             :     /* Perl-ish methods (search is actually Python-esque). */
    3163             :     JS_SELF_HOSTED_FN("match", "String_match",        1,0),
    3164             :     JS_SELF_HOSTED_FN("search", "String_search",      1,0),
    3165             :     JS_SELF_HOSTED_FN("replace", "String_replace",    2,0),
    3166             :     JS_SELF_HOSTED_FN("split",  "String_split",       2,0),
    3167             :     JS_SELF_HOSTED_FN("substr", "String_substr",      2,0),
    3168             : 
    3169             :     /* Python-esque sequence methods. */
    3170             :     JS_FN("concat",            str_concat,            1,0),
    3171             :     JS_SELF_HOSTED_FN("slice", "String_slice",        2,0),
    3172             : 
    3173             :     /* HTML string methods. */
    3174             :     JS_SELF_HOSTED_FN("bold",     "String_bold",       0,0),
    3175             :     JS_SELF_HOSTED_FN("italics",  "String_italics",    0,0),
    3176             :     JS_SELF_HOSTED_FN("fixed",    "String_fixed",      0,0),
    3177             :     JS_SELF_HOSTED_FN("strike",   "String_strike",     0,0),
    3178             :     JS_SELF_HOSTED_FN("small",    "String_small",      0,0),
    3179             :     JS_SELF_HOSTED_FN("big",      "String_big",        0,0),
    3180             :     JS_SELF_HOSTED_FN("blink",    "String_blink",      0,0),
    3181             :     JS_SELF_HOSTED_FN("sup",      "String_sup",        0,0),
    3182             :     JS_SELF_HOSTED_FN("sub",      "String_sub",        0,0),
    3183             :     JS_SELF_HOSTED_FN("anchor",   "String_anchor",     1,0),
    3184             :     JS_SELF_HOSTED_FN("link",     "String_link",       1,0),
    3185             :     JS_SELF_HOSTED_FN("fontcolor","String_fontcolor",  1,0),
    3186             :     JS_SELF_HOSTED_FN("fontsize", "String_fontsize",   1,0),
    3187             : 
    3188             :     JS_SELF_HOSTED_SYM_FN(iterator, "String_iterator", 0,0),
    3189             :     JS_FS_END
    3190             : };
    3191             : 
    3192             : // ES6 rev 27 (2014 Aug 24) 21.1.1
    3193             : bool
    3194           7 : js::StringConstructor(JSContext* cx, unsigned argc, Value* vp)
    3195             : {
    3196           7 :     CallArgs args = CallArgsFromVp(argc, vp);
    3197             : 
    3198          14 :     RootedString str(cx);
    3199           7 :     if (args.length() > 0) {
    3200           7 :         if (!args.isConstructing() && args[0].isSymbol())
    3201           0 :             return js::SymbolDescriptiveString(cx, args[0].toSymbol(), args.rval());
    3202             : 
    3203           7 :         str = ToString<CanGC>(cx, args[0]);
    3204           7 :         if (!str)
    3205           0 :             return false;
    3206             :     } else {
    3207           0 :         str = cx->runtime()->emptyString;
    3208             :     }
    3209             : 
    3210           7 :     if (args.isConstructing()) {
    3211           2 :         RootedObject proto(cx);
    3212           1 :         if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
    3213           0 :             return false;
    3214             : 
    3215           1 :         StringObject* strobj = StringObject::create(cx, str, proto);
    3216           1 :         if (!strobj)
    3217           0 :             return false;
    3218           1 :         args.rval().setObject(*strobj);
    3219           1 :         return true;
    3220             :     }
    3221             : 
    3222           6 :     args.rval().setString(str);
    3223           6 :     return true;
    3224             : }
    3225             : 
    3226             : static bool
    3227           0 : str_fromCharCode_few_args(JSContext* cx, const CallArgs& args)
    3228             : {
    3229           0 :     MOZ_ASSERT(args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE);
    3230             : 
    3231             :     char16_t chars[JSFatInlineString::MAX_LENGTH_TWO_BYTE];
    3232           0 :     for (unsigned i = 0; i < args.length(); i++) {
    3233             :         uint16_t code;
    3234           0 :         if (!ToUint16(cx, args[i], &code))
    3235           0 :             return false;
    3236           0 :         chars[i] = char16_t(code);
    3237             :     }
    3238           0 :     JSString* str = NewStringCopyN<CanGC>(cx, chars, args.length());
    3239           0 :     if (!str)
    3240           0 :         return false;
    3241           0 :     args.rval().setString(str);
    3242           0 :     return true;
    3243             : }
    3244             : 
    3245             : bool
    3246           0 : js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp)
    3247             : {
    3248           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    3249             : 
    3250           0 :     MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
    3251             : 
    3252             :     // Optimize the single-char case.
    3253           0 :     if (args.length() == 1)
    3254           0 :         return str_fromCharCode_one_arg(cx, args[0], args.rval());
    3255             : 
    3256             :     // Optimize the case where the result will definitely fit in an inline
    3257             :     // string (thin or fat) and so we don't need to malloc the chars. (We could
    3258             :     // cover some cases where args.length() goes up to
    3259             :     // JSFatInlineString::MAX_LENGTH_LATIN1 if we also checked if the chars are
    3260             :     // all Latin-1, but it doesn't seem worth the effort.)
    3261           0 :     if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE)
    3262           0 :         return str_fromCharCode_few_args(cx, args);
    3263             : 
    3264           0 :     char16_t* chars = cx->pod_malloc<char16_t>(args.length() + 1);
    3265           0 :     if (!chars)
    3266           0 :         return false;
    3267           0 :     for (unsigned i = 0; i < args.length(); i++) {
    3268             :         uint16_t code;
    3269           0 :         if (!ToUint16(cx, args[i], &code)) {
    3270           0 :             js_free(chars);
    3271           0 :             return false;
    3272             :         }
    3273           0 :         chars[i] = char16_t(code);
    3274             :     }
    3275           0 :     chars[args.length()] = 0;
    3276           0 :     JSString* str = NewString<CanGC>(cx, chars, args.length());
    3277           0 :     if (!str) {
    3278           0 :         js_free(chars);
    3279           0 :         return false;
    3280             :     }
    3281             : 
    3282           0 :     args.rval().setString(str);
    3283           0 :     return true;
    3284             : }
    3285             : 
    3286             : static inline bool
    3287           0 : CodeUnitToString(JSContext* cx, uint16_t ucode, MutableHandleValue rval)
    3288             : {
    3289           0 :     if (StaticStrings::hasUnit(ucode)) {
    3290           0 :         rval.setString(cx->staticStrings().getUnit(ucode));
    3291           0 :         return true;
    3292             :     }
    3293             : 
    3294           0 :     char16_t c = char16_t(ucode);
    3295           0 :     JSString* str = NewStringCopyN<CanGC>(cx, &c, 1);
    3296           0 :     if (!str)
    3297           0 :         return false;
    3298             : 
    3299           0 :     rval.setString(str);
    3300           0 :     return true;
    3301             : }
    3302             : 
    3303             : bool
    3304           0 : js::str_fromCharCode_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval)
    3305             : {
    3306             :     uint16_t ucode;
    3307             : 
    3308           0 :     if (!ToUint16(cx, code, &ucode))
    3309           0 :         return false;
    3310             : 
    3311           0 :     return CodeUnitToString(cx, ucode, rval);
    3312             : }
    3313             : 
    3314             : static MOZ_ALWAYS_INLINE bool
    3315           0 : ToCodePoint(JSContext* cx, HandleValue code, uint32_t* codePoint)
    3316             : {
    3317             :     // String.fromCodePoint, Steps 5.a-b.
    3318             :     double nextCP;
    3319           0 :     if (!ToNumber(cx, code, &nextCP))
    3320           0 :         return false;
    3321             : 
    3322             :     // String.fromCodePoint, Steps 5.c-d.
    3323           0 :     if (JS::ToInteger(nextCP) != nextCP || nextCP < 0 || nextCP > unicode::NonBMPMax) {
    3324           0 :         ToCStringBuf cbuf;
    3325           0 :         if (char* numStr = NumberToCString(cx, &cbuf, nextCP))
    3326           0 :             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_A_CODEPOINT, numStr);
    3327           0 :         return false;
    3328             :     }
    3329             : 
    3330           0 :     *codePoint = uint32_t(nextCP);
    3331           0 :     return true;
    3332             : }
    3333             : 
    3334             : bool
    3335           0 : js::str_fromCodePoint_one_arg(JSContext* cx, HandleValue code, MutableHandleValue rval)
    3336             : {
    3337             :     // Steps 1-4 (omitted).
    3338             : 
    3339             :     // Steps 5.a-d.
    3340             :     uint32_t codePoint;
    3341           0 :     if (!ToCodePoint(cx, code, &codePoint))
    3342           0 :         return false;
    3343             : 
    3344             :     // Steps 5.e, 6.
    3345           0 :     if (!unicode::IsSupplementary(codePoint))
    3346           0 :         return CodeUnitToString(cx, uint16_t(codePoint), rval);
    3347             : 
    3348           0 :     char16_t chars[] = { unicode::LeadSurrogate(codePoint), unicode::TrailSurrogate(codePoint) };
    3349           0 :     JSString* str = NewStringCopyNDontDeflate<CanGC>(cx, chars, 2);
    3350           0 :     if (!str)
    3351           0 :         return false;
    3352             : 
    3353           0 :     rval.setString(str);
    3354           0 :     return true;
    3355             : }
    3356             : 
    3357             : static bool
    3358           0 : str_fromCodePoint_few_args(JSContext* cx, const CallArgs& args)
    3359             : {
    3360           0 :     MOZ_ASSERT(args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE / 2);
    3361             : 
    3362             :     // Steps 1-2 (omitted).
    3363             : 
    3364             :     // Step 3.
    3365             :     char16_t elements[JSFatInlineString::MAX_LENGTH_TWO_BYTE];
    3366             : 
    3367             :     // Steps 4-5.
    3368           0 :     unsigned length = 0;
    3369           0 :     for (unsigned nextIndex = 0; nextIndex < args.length(); nextIndex++) {
    3370             :         // Steps 5.a-d.
    3371             :         uint32_t codePoint;
    3372           0 :         if (!ToCodePoint(cx, args[nextIndex], &codePoint))
    3373           0 :             return false;
    3374             : 
    3375             :         // Step 5.e.
    3376           0 :         unicode::UTF16Encode(codePoint, elements, &length);
    3377             :     }
    3378             : 
    3379             :     // Step 6.
    3380           0 :     JSString* str = NewStringCopyN<CanGC>(cx, elements, length);
    3381           0 :     if (!str)
    3382           0 :         return false;
    3383             : 
    3384           0 :     args.rval().setString(str);
    3385           0 :     return true;
    3386             : }
    3387             : 
    3388             : // ES2017 draft rev 40edb3a95a475c1b251141ac681b8793129d9a6d
    3389             : // 21.1.2.2 String.fromCodePoint(...codePoints)
    3390             : bool
    3391           0 : js::str_fromCodePoint(JSContext* cx, unsigned argc, Value* vp)
    3392             : {
    3393           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    3394             : 
    3395             :     // Optimize the single code-point case.
    3396           0 :     if (args.length() == 1)
    3397           0 :         return str_fromCodePoint_one_arg(cx, args[0], args.rval());
    3398             : 
    3399             :     // Optimize the case where the result will definitely fit in an inline
    3400             :     // string (thin or fat) and so we don't need to malloc the chars. (We could
    3401             :     // cover some cases where |args.length()| goes up to
    3402             :     // JSFatInlineString::MAX_LENGTH_LATIN1 / 2 if we also checked if the chars
    3403             :     // are all Latin-1, but it doesn't seem worth the effort.)
    3404           0 :     if (args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE / 2)
    3405           0 :         return str_fromCodePoint_few_args(cx, args);
    3406             : 
    3407             :     // Steps 1-2 (omitted).
    3408             : 
    3409             :     // Step 3.
    3410             :     static_assert(ARGS_LENGTH_MAX < std::numeric_limits<size_t>::max() / 2,
    3411             :                   "|args.length() * 2 + 1| does not overflow");
    3412           0 :     char16_t* elements = cx->pod_malloc<char16_t>(args.length() * 2 + 1);
    3413           0 :     if (!elements)
    3414           0 :         return false;
    3415             : 
    3416             :     // Steps 4-5.
    3417           0 :     unsigned length = 0;
    3418           0 :     for (unsigned nextIndex = 0; nextIndex < args.length(); nextIndex++) {
    3419             :         // Steps 5.a-d.
    3420             :         uint32_t codePoint;
    3421           0 :         if (!ToCodePoint(cx, args[nextIndex], &codePoint)) {
    3422           0 :             js_free(elements);
    3423           0 :             return false;
    3424             :         }
    3425             : 
    3426             :         // Step 5.e.
    3427           0 :         unicode::UTF16Encode(codePoint, elements, &length);
    3428             :     }
    3429           0 :     elements[length] = 0;
    3430             : 
    3431             :     // Step 6.
    3432           0 :     JSString* str = NewString<CanGC>(cx, elements, length);
    3433           0 :     if (!str) {
    3434           0 :         js_free(elements);
    3435           0 :         return false;
    3436             :     }
    3437             : 
    3438           0 :     args.rval().setString(str);
    3439           0 :     return true;
    3440             : }
    3441             : 
    3442             : static const JSFunctionSpec string_static_methods[] = {
    3443             :     JS_INLINABLE_FN("fromCharCode", js::str_fromCharCode, 1, 0, StringFromCharCode),
    3444             :     JS_INLINABLE_FN("fromCodePoint", js::str_fromCodePoint, 1, 0, StringFromCodePoint),
    3445             : 
    3446             :     JS_SELF_HOSTED_FN("raw",             "String_static_raw",           2,0),
    3447             :     JS_SELF_HOSTED_FN("substring",       "String_static_substring",     3,0),
    3448             :     JS_SELF_HOSTED_FN("substr",          "String_static_substr",        3,0),
    3449             :     JS_SELF_HOSTED_FN("slice",           "String_static_slice",         3,0),
    3450             : 
    3451             :     JS_SELF_HOSTED_FN("match",           "String_generic_match",        2,0),
    3452             :     JS_SELF_HOSTED_FN("replace",         "String_generic_replace",      3,0),
    3453             :     JS_SELF_HOSTED_FN("search",          "String_generic_search",       2,0),
    3454             :     JS_SELF_HOSTED_FN("split",           "String_generic_split",        3,0),
    3455             : 
    3456             :     JS_SELF_HOSTED_FN("toLowerCase",     "String_static_toLowerCase",   1,0),
    3457             :     JS_SELF_HOSTED_FN("toUpperCase",     "String_static_toUpperCase",   1,0),
    3458             :     JS_SELF_HOSTED_FN("charAt",          "String_static_charAt",        2,0),
    3459             :     JS_SELF_HOSTED_FN("charCodeAt",      "String_static_charCodeAt",    2,0),
    3460             :     JS_SELF_HOSTED_FN("includes",        "String_static_includes",      2,0),
    3461             :     JS_SELF_HOSTED_FN("indexOf",         "String_static_indexOf",       2,0),
    3462             :     JS_SELF_HOSTED_FN("lastIndexOf",     "String_static_lastIndexOf",   2,0),
    3463             :     JS_SELF_HOSTED_FN("startsWith",      "String_static_startsWith",    2,0),
    3464             :     JS_SELF_HOSTED_FN("endsWith",        "String_static_endsWith",      2,0),
    3465             :     JS_SELF_HOSTED_FN("trim",            "String_static_trim",          1,0),
    3466             :     JS_SELF_HOSTED_FN("trimLeft",        "String_static_trimLeft",      1,0),
    3467             :     JS_SELF_HOSTED_FN("trimRight",       "String_static_trimRight",     1,0),
    3468             :     JS_SELF_HOSTED_FN("toLocaleLowerCase","String_static_toLocaleLowerCase",1,0),
    3469             :     JS_SELF_HOSTED_FN("toLocaleUpperCase","String_static_toLocaleUpperCase",1,0),
    3470             : #if EXPOSE_INTL_API
    3471             :     JS_SELF_HOSTED_FN("normalize",       "String_static_normalize",     1,0),
    3472             : #endif
    3473             :     JS_SELF_HOSTED_FN("concat",          "String_static_concat",        2,0),
    3474             : 
    3475             :     JS_SELF_HOSTED_FN("localeCompare",   "String_static_localeCompare", 2,0),
    3476             :     JS_FS_END
    3477             : };
    3478             : 
    3479             : /* static */ Shape*
    3480          49 : StringObject::assignInitialShape(JSContext* cx, Handle<StringObject*> obj)
    3481             : {
    3482          49 :     MOZ_ASSERT(obj->empty());
    3483             : 
    3484          98 :     return NativeObject::addDataProperty(cx, obj, cx->names().length, LENGTH_SLOT,
    3485          98 :                                          JSPROP_PERMANENT | JSPROP_READONLY);
    3486             : }
    3487             : 
    3488             : JSObject*
    3489          48 : js::InitStringClass(JSContext* cx, HandleObject obj)
    3490             : {
    3491          48 :     MOZ_ASSERT(obj->isNative());
    3492             : 
    3493          48 :     Handle<GlobalObject*> global = obj.as<GlobalObject>();
    3494             : 
    3495          96 :     Rooted<JSString*> empty(cx, cx->runtime()->emptyString);
    3496          96 :     RootedObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &StringObject::class_));
    3497          48 :     if (!proto)
    3498           0 :         return nullptr;
    3499          48 :     Handle<StringObject*> protoObj = proto.as<StringObject>();
    3500          48 :     if (!StringObject::init(cx, protoObj, empty))
    3501           0 :         return nullptr;
    3502             : 
    3503             :     /* Now create the String function. */
    3504          96 :     RootedFunction ctor(cx);
    3505         144 :     ctor = GlobalObject::createConstructor(cx, StringConstructor, cx->names().String, 1,
    3506          96 :                                            AllocKind::FUNCTION, &jit::JitInfo_String);
    3507          48 :     if (!ctor)
    3508           0 :         return nullptr;
    3509             : 
    3510          48 :     if (!LinkConstructorAndPrototype(cx, ctor, proto))
    3511           0 :         return nullptr;
    3512             : 
    3513         192 :     if (!DefinePropertiesAndFunctions(cx, proto, nullptr, string_methods) ||
    3514         144 :         !DefinePropertiesAndFunctions(cx, ctor, nullptr, string_static_methods))
    3515             :     {
    3516           0 :         return nullptr;
    3517             :     }
    3518             : 
    3519             :     /*
    3520             :      * Define escape/unescape, the URI encode/decode functions, and maybe
    3521             :      * uneval on the global object.
    3522             :      */
    3523          48 :     if (!JS_DefineFunctions(cx, global, string_functions))
    3524           0 :         return nullptr;
    3525             : 
    3526          48 :     if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto))
    3527           0 :         return nullptr;
    3528             : 
    3529          48 :     return proto;
    3530             : }
    3531             : 
    3532             : const char*
    3533           0 : js::ValueToPrintable(JSContext* cx, const Value& vArg, JSAutoByteString* bytes, bool asSource)
    3534             : {
    3535           0 :     RootedValue v(cx, vArg);
    3536             :     JSString* str;
    3537           0 :     if (asSource)
    3538           0 :         str = ValueToSource(cx, v);
    3539             :     else
    3540           0 :         str = ToString<CanGC>(cx, v);
    3541           0 :     if (!str)
    3542           0 :         return nullptr;
    3543           0 :     str = QuoteString(cx, str, 0);
    3544           0 :     if (!str)
    3545           0 :         return nullptr;
    3546           0 :     return bytes->encodeLatin1(cx, str);
    3547             : }
    3548             : 
    3549             : template <AllowGC allowGC>
    3550             : JSString*
    3551          90 : js::ToStringSlow(JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
    3552             : {
    3553             :     /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
    3554          90 :     MOZ_ASSERT(!arg.isString());
    3555             : 
    3556          90 :     Value v = arg;
    3557          90 :     if (!v.isPrimitive()) {
    3558           2 :         MOZ_ASSERT(!cx->helperThread());
    3559             :         if (!allowGC)
    3560           0 :             return nullptr;
    3561           4 :         RootedValue v2(cx, v);
    3562           2 :         if (!ToPrimitive(cx, JSTYPE_STRING, &v2))
    3563           0 :             return nullptr;
    3564           2 :         v = v2;
    3565             :     }
    3566             : 
    3567             :     JSString* str;
    3568          90 :     if (v.isString()) {
    3569           2 :         str = v.toString();
    3570          88 :     } else if (v.isInt32()) {
    3571          45 :         str = Int32ToString<allowGC>(cx, v.toInt32());
    3572          43 :     } else if (v.isDouble()) {
    3573           8 :         str = NumberToString<allowGC>(cx, v.toDouble());
    3574          35 :     } else if (v.isBoolean()) {
    3575          29 :         str = BooleanToString(cx, v.toBoolean());
    3576           6 :     } else if (v.isNull()) {
    3577           5 :         str = cx->names().null;
    3578           1 :     } else if (v.isSymbol()) {
    3579           0 :         MOZ_ASSERT(!cx->helperThread());
    3580             :         if (allowGC) {
    3581           0 :             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    3582             :                                       JSMSG_SYMBOL_TO_STRING);
    3583             :         }
    3584           0 :         return nullptr;
    3585             :     } else {
    3586           1 :         MOZ_ASSERT(v.isUndefined());
    3587           1 :         str = cx->names().undefined;
    3588             :     }
    3589          90 :     return str;
    3590             : }
    3591             : 
    3592             : template JSString*
    3593             : js::ToStringSlow<CanGC>(JSContext* cx, HandleValue arg);
    3594             : 
    3595             : template JSString*
    3596             : js::ToStringSlow<NoGC>(JSContext* cx, const Value& arg);
    3597             : 
    3598             : JS_PUBLIC_API(JSString*)
    3599          28 : js::ToStringSlow(JSContext* cx, HandleValue v)
    3600             : {
    3601          28 :     return ToStringSlow<CanGC>(cx, v);
    3602             : }
    3603             : 
    3604             : static JSString*
    3605           0 : SymbolToSource(JSContext* cx, Symbol* symbol)
    3606             : {
    3607           0 :     RootedString desc(cx, symbol->description());
    3608           0 :     SymbolCode code = symbol->code();
    3609           0 :     if (code != SymbolCode::InSymbolRegistry && code != SymbolCode::UniqueSymbol) {
    3610             :         // Well-known symbol.
    3611           0 :         MOZ_ASSERT(uint32_t(code) < JS::WellKnownSymbolLimit);
    3612           0 :         return desc;
    3613             :     }
    3614             : 
    3615           0 :     StringBuffer buf(cx);
    3616           0 :     if (code == SymbolCode::InSymbolRegistry ? !buf.append("Symbol.for(") : !buf.append("Symbol("))
    3617           0 :         return nullptr;
    3618           0 :     if (desc) {
    3619           0 :         desc = StringToSource(cx, desc);
    3620           0 :         if (!desc || !buf.append(desc))
    3621           0 :             return nullptr;
    3622             :     }
    3623           0 :     if (!buf.append(')'))
    3624           0 :         return nullptr;
    3625           0 :     return buf.finishString();
    3626             : }
    3627             : 
    3628             : JSString*
    3629           3 : js::ValueToSource(JSContext* cx, HandleValue v)
    3630             : {
    3631           3 :     if (!CheckRecursionLimit(cx))
    3632           0 :         return nullptr;
    3633           3 :     assertSameCompartment(cx, v);
    3634             : 
    3635           3 :     if (v.isUndefined())
    3636           0 :         return cx->names().void0;
    3637           3 :     if (v.isString())
    3638           3 :         return StringToSource(cx, v.toString());
    3639           0 :     if (v.isSymbol())
    3640           0 :         return SymbolToSource(cx, v.toSymbol());
    3641           0 :     if (v.isPrimitive()) {
    3642             :         /* Special case to preserve negative zero, _contra_ toString. */
    3643           0 :         if (v.isDouble() && IsNegativeZero(v.toDouble())) {
    3644             :             /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
    3645             :             static const char16_t js_negzero_ucNstr[] = {'-', '0'};
    3646             : 
    3647           0 :             return NewStringCopyN<CanGC>(cx, js_negzero_ucNstr, 2);
    3648             :         }
    3649           0 :         return ToString<CanGC>(cx, v);
    3650             :     }
    3651             : 
    3652           0 :     RootedValue fval(cx);
    3653           0 :     RootedObject obj(cx, &v.toObject());
    3654           0 :     if (!GetProperty(cx, obj, obj, cx->names().toSource, &fval))
    3655           0 :         return nullptr;
    3656           0 :     if (IsCallable(fval)) {
    3657           0 :         RootedValue v(cx);
    3658           0 :         if (!js::Call(cx, fval, obj, &v))
    3659           0 :             return nullptr;
    3660             : 
    3661           0 :         return ToString<CanGC>(cx, v);
    3662             :     }
    3663             : 
    3664           0 :     return ObjectToSource(cx, obj);
    3665             : }
    3666             : 
    3667             : JSString*
    3668           3 : js::StringToSource(JSContext* cx, JSString* str)
    3669             : {
    3670           3 :     return QuoteString(cx, str, '"');
    3671             : }
    3672             : 
    3673             : bool
    3674        1332 : js::EqualChars(JSLinearString* str1, JSLinearString* str2)
    3675             : {
    3676        1332 :     MOZ_ASSERT(str1->length() == str2->length());
    3677             : 
    3678        1332 :     size_t len = str1->length();
    3679             : 
    3680        2664 :     AutoCheckCannotGC nogc;
    3681        1332 :     if (str1->hasTwoByteChars()) {
    3682         102 :         if (str2->hasTwoByteChars())
    3683           1 :             return PodEqual(str1->twoByteChars(nogc), str2->twoByteChars(nogc), len);
    3684             : 
    3685         101 :         return EqualChars(str2->latin1Chars(nogc), str1->twoByteChars(nogc), len);
    3686             :     }
    3687             : 
    3688        1230 :     if (str2->hasLatin1Chars())
    3689        1228 :         return PodEqual(str1->latin1Chars(nogc), str2->latin1Chars(nogc), len);
    3690             : 
    3691           2 :     return EqualChars(str1->latin1Chars(nogc), str2->twoByteChars(nogc), len);
    3692             : }
    3693             : 
    3694             : bool
    3695        7159 : js::EqualStrings(JSContext* cx, JSString* str1, JSString* str2, bool* result)
    3696             : {
    3697        7159 :     if (str1 == str2) {
    3698        2698 :         *result = true;
    3699        2698 :         return true;
    3700             :     }
    3701             : 
    3702        4461 :     size_t length1 = str1->length();
    3703        4461 :     if (length1 != str2->length()) {
    3704        3537 :         *result = false;
    3705        3537 :         return true;
    3706             :     }
    3707             : 
    3708         924 :     JSLinearString* linear1 = str1->ensureLinear(cx);
    3709         924 :     if (!linear1)
    3710           0 :         return false;
    3711         924 :     JSLinearString* linear2 = str2->ensureLinear(cx);
    3712         924 :     if (!linear2)
    3713           0 :         return false;
    3714             : 
    3715         924 :     *result = EqualChars(linear1, linear2);
    3716         924 :     return true;
    3717             : }
    3718             : 
    3719             : bool
    3720         273 : js::EqualStrings(JSLinearString* str1, JSLinearString* str2)
    3721             : {
    3722         273 :     if (str1 == str2)
    3723           0 :         return true;
    3724             : 
    3725         273 :     size_t length1 = str1->length();
    3726         273 :     if (length1 != str2->length())
    3727         273 :         return false;
    3728             : 
    3729           0 :     return EqualChars(str1, str2);
    3730             : }
    3731             : 
    3732             : static int32_t
    3733          72 : CompareStringsImpl(JSLinearString* str1, JSLinearString* str2)
    3734             : {
    3735          72 :     size_t len1 = str1->length();
    3736          72 :     size_t len2 = str2->length();
    3737             : 
    3738         144 :     AutoCheckCannotGC nogc;
    3739          72 :     if (str1->hasLatin1Chars()) {
    3740          72 :         const Latin1Char* chars1 = str1->latin1Chars(nogc);
    3741          72 :         return str2->hasLatin1Chars()
    3742          72 :                ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2)
    3743          72 :                : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2);
    3744             :     }
    3745             : 
    3746           0 :     const char16_t* chars1 = str1->twoByteChars(nogc);
    3747           0 :     return str2->hasLatin1Chars()
    3748           0 :            ? CompareChars(chars1, len1, str2->latin1Chars(nogc), len2)
    3749           0 :            : CompareChars(chars1, len1, str2->twoByteChars(nogc), len2);
    3750             : }
    3751             : 
    3752             : int32_t
    3753           0 : js::CompareChars(const char16_t* s1, size_t len1, JSLinearString* s2)
    3754             : {
    3755           0 :     AutoCheckCannotGC nogc;
    3756           0 :     return s2->hasLatin1Chars()
    3757           0 :            ? CompareChars(s1, len1, s2->latin1Chars(nogc), s2->length())
    3758           0 :            : CompareChars(s1, len1, s2->twoByteChars(nogc), s2->length());
    3759             : }
    3760             : 
    3761             : bool
    3762          72 : js::CompareStrings(JSContext* cx, JSString* str1, JSString* str2, int32_t* result)
    3763             : {
    3764          72 :     MOZ_ASSERT(str1);
    3765          72 :     MOZ_ASSERT(str2);
    3766             : 
    3767          72 :     if (str1 == str2) {
    3768           0 :         *result = 0;
    3769           0 :         return true;
    3770             :     }
    3771             : 
    3772          72 :     JSLinearString* linear1 = str1->ensureLinear(cx);
    3773          72 :     if (!linear1)
    3774           0 :         return false;
    3775             : 
    3776          72 :     JSLinearString* linear2 = str2->ensureLinear(cx);
    3777          72 :     if (!linear2)
    3778           0 :         return false;
    3779             : 
    3780          72 :     *result = CompareStringsImpl(linear1, linear2);
    3781          72 :     return true;
    3782             : }
    3783             : 
    3784             : int32_t
    3785           0 : js::CompareAtoms(JSAtom* atom1, JSAtom* atom2)
    3786             : {
    3787           0 :     return CompareStringsImpl(atom1, atom2);
    3788             : }
    3789             : 
    3790             : bool
    3791         797 : js::StringEqualsAscii(JSLinearString* str, const char* asciiBytes)
    3792             : {
    3793         797 :     size_t length = strlen(asciiBytes);
    3794             : #ifdef DEBUG
    3795       10179 :     for (size_t i = 0; i != length; ++i)
    3796        9382 :         MOZ_ASSERT(unsigned(asciiBytes[i]) <= 127);
    3797             : #endif
    3798         797 :     if (length != str->length())
    3799         679 :         return false;
    3800             : 
    3801         118 :     const Latin1Char* latin1 = reinterpret_cast<const Latin1Char*>(asciiBytes);
    3802             : 
    3803         236 :     AutoCheckCannotGC nogc;
    3804         118 :     return str->hasLatin1Chars()
    3805         118 :            ? PodEqual(latin1, str->latin1Chars(nogc), length)
    3806         118 :            : EqualChars(latin1, str->twoByteChars(nogc), length);
    3807             : }
    3808             : 
    3809             : size_t
    3810         837 : js_strlen(const char16_t* s)
    3811             : {
    3812             :     const char16_t* t;
    3813             : 
    3814       40941 :     for (t = s; *t != 0; t++)
    3815       40104 :         continue;
    3816         837 :     return (size_t)(t - s);
    3817             : }
    3818             : 
    3819             : int32_t
    3820           0 : js_strcmp(const char16_t* lhs, const char16_t* rhs)
    3821             : {
    3822             :     while (true) {
    3823           0 :         if (*lhs != *rhs)
    3824           0 :             return int32_t(*lhs) - int32_t(*rhs);
    3825           0 :         if (*lhs == 0)
    3826           0 :             return 0;
    3827           0 :         ++lhs; ++rhs;
    3828             :     }
    3829             : }
    3830             : 
    3831             : int32_t
    3832           0 : js_fputs(const char16_t* s, FILE* f)
    3833             : {
    3834           0 :     while (*s != 0) {
    3835           0 :         if (fputwc(wchar_t(*s), f) == WEOF)
    3836           0 :             return WEOF;
    3837           0 :         s++;
    3838             :     }
    3839           0 :     return 1;
    3840             : }
    3841             : 
    3842             : UniqueChars
    3843        1784 : js::DuplicateString(JSContext* cx, const char* s)
    3844             : {
    3845        1784 :     size_t n = strlen(s) + 1;
    3846        1784 :     auto ret = cx->make_pod_array<char>(n);
    3847        1784 :     if (!ret)
    3848           0 :         return ret;
    3849        1784 :     PodCopy(ret.get(), s, n);
    3850        1784 :     return ret;
    3851             : }
    3852             : 
    3853             : UniqueTwoByteChars
    3854           2 : js::DuplicateString(JSContext* cx, const char16_t* s)
    3855             : {
    3856           2 :     size_t n = js_strlen(s) + 1;
    3857           2 :     auto ret = cx->make_pod_array<char16_t>(n);
    3858           2 :     if (!ret)
    3859           0 :         return ret;
    3860           2 :     PodCopy(ret.get(), s, n);
    3861           2 :     return ret;
    3862             : }
    3863             : 
    3864             : UniqueChars
    3865           0 : js::DuplicateString(const char* s)
    3866             : {
    3867           0 :     return UniqueChars(js_strdup(s));
    3868             : }
    3869             : 
    3870             : UniqueChars
    3871           0 : js::DuplicateString(const char* s, size_t n)
    3872             : {
    3873           0 :     UniqueChars ret(js_pod_malloc<char>(n + 1));
    3874           0 :     if (!ret)
    3875           0 :         return nullptr;
    3876           0 :     PodCopy(ret.get(), s, n);
    3877           0 :     ret[n] = 0;
    3878           0 :     return ret;
    3879             : }
    3880             : 
    3881             : UniqueTwoByteChars
    3882           0 : js::DuplicateString(const char16_t* s)
    3883             : {
    3884           0 :     return DuplicateString(s, js_strlen(s));
    3885             : }
    3886             : 
    3887             : UniqueTwoByteChars
    3888          95 : js::DuplicateString(const char16_t* s, size_t n)
    3889             : {
    3890         190 :     UniqueTwoByteChars ret(js_pod_malloc<char16_t>(n + 1));
    3891          95 :     if (!ret)
    3892           0 :         return nullptr;
    3893          95 :     PodCopy(ret.get(), s, n);
    3894          95 :     ret[n] = 0;
    3895          95 :     return ret;
    3896             : }
    3897             : 
    3898             : template <typename CharT>
    3899             : const CharT*
    3900         280 : js_strchr_limit(const CharT* s, char16_t c, const CharT* limit)
    3901             : {
    3902         532 :     while (s < limit) {
    3903         252 :         if (*s == c)
    3904           0 :             return s;
    3905         252 :         s++;
    3906             :     }
    3907          28 :     return nullptr;
    3908             : }
    3909             : 
    3910             : template const Latin1Char*
    3911             : js_strchr_limit(const Latin1Char* s, char16_t c, const Latin1Char* limit);
    3912             : 
    3913             : template const char16_t*
    3914             : js_strchr_limit(const char16_t* s, char16_t c, const char16_t* limit);
    3915             : 
    3916             : char16_t*
    3917         428 : js::InflateString(JSContext* cx, const char* bytes, size_t* lengthp)
    3918             : {
    3919             :     size_t nchars;
    3920             :     char16_t* chars;
    3921         428 :     size_t nbytes = *lengthp;
    3922             : 
    3923         428 :     nchars = nbytes;
    3924         428 :     chars = cx->pod_malloc<char16_t>(nchars + 1);
    3925         428 :     if (!chars)
    3926           0 :         goto bad;
    3927     3315994 :     for (size_t i = 0; i < nchars; i++)
    3928     3315566 :         chars[i] = (unsigned char) bytes[i];
    3929         428 :     *lengthp = nchars;
    3930         428 :     chars[nchars] = 0;
    3931         428 :     return chars;
    3932             : 
    3933             :   bad:
    3934             :     // For compatibility with callers of JS_DecodeBytes we must zero lengthp
    3935             :     // on errors.
    3936           0 :     *lengthp = 0;
    3937           0 :     return nullptr;
    3938             : }
    3939             : 
    3940             : template <typename CharT>
    3941             : bool
    3942        3914 : js::DeflateStringToBuffer(JSContext* maybecx, const CharT* src, size_t srclen,
    3943             :                           char* dst, size_t* dstlenp)
    3944             : {
    3945        3914 :     size_t dstlen = *dstlenp;
    3946        3914 :     if (srclen > dstlen) {
    3947           0 :         for (size_t i = 0; i < dstlen; i++)
    3948           0 :             dst[i] = char(src[i]);
    3949           0 :         if (maybecx) {
    3950           0 :             AutoSuppressGC suppress(maybecx);
    3951           0 :             JS_ReportErrorNumberASCII(maybecx, GetErrorMessage, nullptr,
    3952             :                                       JSMSG_BUFFER_TOO_SMALL);
    3953             :         }
    3954           0 :         return false;
    3955             :     }
    3956      139369 :     for (size_t i = 0; i < srclen; i++)
    3957      135455 :         dst[i] = char(src[i]);
    3958        3914 :     *dstlenp = srclen;
    3959        3914 :     return true;
    3960             : }
    3961             : 
    3962             : template bool
    3963             : js::DeflateStringToBuffer(JSContext* maybecx, const Latin1Char* src, size_t srclen,
    3964             :                           char* dst, size_t* dstlenp);
    3965             : 
    3966             : template bool
    3967             : js::DeflateStringToBuffer(JSContext* maybecx, const char16_t* src, size_t srclen,
    3968             :                           char* dst, size_t* dstlenp);
    3969             : 
    3970             : #define ____ false
    3971             : 
    3972             : /*
    3973             :  * Identifier start chars:
    3974             :  * -      36:    $
    3975             :  * -  65..90: A..Z
    3976             :  * -      95:    _
    3977             :  * - 97..122: a..z
    3978             :  */
    3979             : const bool js_isidstart[] = {
    3980             : /*       0     1     2     3     4     5     6     7     8     9  */
    3981             : /*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    3982             : /*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    3983             : /*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    3984             : /*  3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
    3985             : /*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    3986             : /*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    3987             : /*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
    3988             : /*  7 */ true, true, true, true, true, true, true, true, true, true,
    3989             : /*  8 */ true, true, true, true, true, true, true, true, true, true,
    3990             : /*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
    3991             : /* 10 */ true, true, true, true, true, true, true, true, true, true,
    3992             : /* 11 */ true, true, true, true, true, true, true, true, true, true,
    3993             : /* 12 */ true, true, true, ____, ____, ____, ____, ____
    3994             : };
    3995             : 
    3996             : /*
    3997             :  * Identifier chars:
    3998             :  * -      36:    $
    3999             :  * -  48..57: 0..9
    4000             :  * -  65..90: A..Z
    4001             :  * -      95:    _
    4002             :  * - 97..122: a..z
    4003             :  */
    4004             : const bool js_isident[] = {
    4005             : /*       0     1     2     3     4     5     6     7     8     9  */
    4006             : /*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4007             : /*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4008             : /*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4009             : /*  3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
    4010             : /*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
    4011             : /*  5 */ true, true, true, true, true, true, true, true, ____, ____,
    4012             : /*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
    4013             : /*  7 */ true, true, true, true, true, true, true, true, true, true,
    4014             : /*  8 */ true, true, true, true, true, true, true, true, true, true,
    4015             : /*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
    4016             : /* 10 */ true, true, true, true, true, true, true, true, true, true,
    4017             : /* 11 */ true, true, true, true, true, true, true, true, true, true,
    4018             : /* 12 */ true, true, true, ____, ____, ____, ____, ____
    4019             : };
    4020             : 
    4021             : /* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
    4022             : const bool js_isspace[] = {
    4023             : /*       0     1     2     3     4     5     6     7     8     9  */
    4024             : /*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
    4025             : /*  1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
    4026             : /*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4027             : /*  3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
    4028             : /*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4029             : /*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4030             : /*  6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4031             : /*  7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4032             : /*  8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4033             : /*  9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4034             : /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4035             : /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4036             : /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
    4037             : };
    4038             : 
    4039             : /*
    4040             :  * Uri reserved chars + #:
    4041             :  * - 35: #
    4042             :  * - 36: $
    4043             :  * - 38: &
    4044             :  * - 43: +
    4045             :  * - 44: ,
    4046             :  * - 47: /
    4047             :  * - 58: :
    4048             :  * - 59: ;
    4049             :  * - 61: =
    4050             :  * - 63: ?
    4051             :  * - 64: @
    4052             :  */
    4053             : static const bool js_isUriReservedPlusPound[] = {
    4054             : /*       0     1     2     3     4     5     6     7     8     9  */
    4055             : /*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4056             : /*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4057             : /*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4058             : /*  3 */ ____, ____, ____, ____, ____, true, true, ____, true, ____,
    4059             : /*  4 */ ____, ____, ____, true, true, ____, ____, true, ____, ____,
    4060             : /*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
    4061             : /*  6 */ ____, true, ____, true, true, ____, ____, ____, ____, ____,
    4062             : /*  7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4063             : /*  8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4064             : /*  9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4065             : /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4066             : /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4067             : /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
    4068             : };
    4069             : 
    4070             : /*
    4071             :  * Uri unescaped chars:
    4072             :  * -      33: !
    4073             :  * -      39: '
    4074             :  * -      40: (
    4075             :  * -      41: )
    4076             :  * -      42: *
    4077             :  * -      45: -
    4078             :  * -      46: .
    4079             :  * -  48..57: 0-9
    4080             :  * -  65..90: A-Z
    4081             :  * -      95: _
    4082             :  * - 97..122: a-z
    4083             :  * -     126: ~
    4084             :  */
    4085             : static const bool js_isUriUnescaped[] = {
    4086             : /*       0     1     2     3     4     5     6     7     8     9  */
    4087             : /*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4088             : /*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4089             : /*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
    4090             : /*  3 */ ____, ____, ____, true, ____, ____, ____, ____, ____, true,
    4091             : /*  4 */ true, true, true, ____, ____, true, true, ____, true, true,
    4092             : /*  5 */ true, true, true, true, true, true, true, true, ____, ____,
    4093             : /*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
    4094             : /*  7 */ true, true, true, true, true, true, true, true, true, true,
    4095             : /*  8 */ true, true, true, true, true, true, true, true, true, true,
    4096             : /*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
    4097             : /* 10 */ true, true, true, true, true, true, true, true, true, true,
    4098             : /* 11 */ true, true, true, true, true, true, true, true, true, true,
    4099             : /* 12 */ true, true, true, ____, ____, ____, true, ____
    4100             : };
    4101             : 
    4102             : #undef ____
    4103             : 
    4104             : #define URI_CHUNK 64U
    4105             : 
    4106             : static inline bool
    4107         511 : TransferBufferToString(StringBuffer& sb, MutableHandleValue rval)
    4108             : {
    4109         511 :     JSString* str = sb.finishString();
    4110         511 :     if (!str)
    4111           0 :         return false;
    4112         511 :     rval.setString(str);
    4113         511 :     return true;
    4114             : }
    4115             : 
    4116             : /*
    4117             :  * ECMA 3, 15.1.3 URI Handling Function Properties
    4118             :  *
    4119             :  * The following are implementations of the algorithms
    4120             :  * given in the ECMA specification for the hidden functions
    4121             :  * 'Encode' and 'Decode'.
    4122             :  */
    4123             : enum EncodeResult { Encode_Failure, Encode_BadUri, Encode_Success };
    4124             : 
    4125             : template <typename CharT>
    4126             : static EncodeResult
    4127         510 : Encode(StringBuffer& sb, const CharT* chars, size_t length,
    4128             :        const bool* unescapedSet, const bool* unescapedSet2)
    4129             : {
    4130             :     static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
    4131             : 
    4132             :     char16_t hexBuf[4];
    4133         510 :     hexBuf[0] = '%';
    4134         510 :     hexBuf[3] = 0;
    4135             : 
    4136        8040 :     for (size_t k = 0; k < length; k++) {
    4137        7530 :         char16_t c = chars[k];
    4138        7530 :         if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) {
    4139       14520 :             if (!sb.append(c))
    4140           0 :                 return Encode_Failure;
    4141             :         } else {
    4142         270 :             if (unicode::IsTrailSurrogate(c))
    4143           0 :                 return Encode_BadUri;
    4144             : 
    4145             :             uint32_t v;
    4146         270 :             if (!unicode::IsLeadSurrogate(c)) {
    4147         270 :                 v = c;
    4148             :             } else {
    4149           0 :                 k++;
    4150           0 :                 if (k == length)
    4151           0 :                     return Encode_BadUri;
    4152             : 
    4153           0 :                 char16_t c2 = chars[k];
    4154           0 :                 if (!unicode::IsTrailSurrogate(c2))
    4155           0 :                     return Encode_BadUri;
    4156             : 
    4157           0 :                 v = unicode::UTF16Decode(c, c2);
    4158             :             }
    4159             :             uint8_t utf8buf[4];
    4160         270 :             size_t L = OneUcs4ToUtf8Char(utf8buf, v);
    4161         540 :             for (size_t j = 0; j < L; j++) {
    4162         270 :                 hexBuf[1] = HexDigits[utf8buf[j] >> 4];
    4163         270 :                 hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
    4164         270 :                 if (!sb.append(hexBuf, 3))
    4165           0 :                     return Encode_Failure;
    4166             :             }
    4167             :         }
    4168             :     }
    4169             : 
    4170         510 :     return Encode_Success;
    4171             : }
    4172             : 
    4173             : static bool
    4174         510 : Encode(JSContext* cx, HandleLinearString str, const bool* unescapedSet,
    4175             :        const bool* unescapedSet2, MutableHandleValue rval)
    4176             : {
    4177         510 :     size_t length = str->length();
    4178         510 :     if (length == 0) {
    4179           0 :         rval.setString(cx->runtime()->emptyString);
    4180           0 :         return true;
    4181             :     }
    4182             : 
    4183        1020 :     StringBuffer sb(cx);
    4184         510 :     if (!sb.reserve(length))
    4185           0 :         return false;
    4186             : 
    4187             :     EncodeResult res;
    4188         510 :     if (str->hasLatin1Chars()) {
    4189        1020 :         AutoCheckCannotGC nogc;
    4190         510 :         res = Encode(sb, str->latin1Chars(nogc), str->length(), unescapedSet, unescapedSet2);
    4191             :     } else {
    4192           0 :         AutoCheckCannotGC nogc;
    4193           0 :         res = Encode(sb, str->twoByteChars(nogc), str->length(), unescapedSet, unescapedSet2);
    4194             :     }
    4195             : 
    4196         510 :     if (res == Encode_Failure)
    4197           0 :         return false;
    4198             : 
    4199         510 :     if (res == Encode_BadUri) {
    4200           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
    4201           0 :         return false;
    4202             :     }
    4203             : 
    4204         510 :     MOZ_ASSERT(res == Encode_Success);
    4205         510 :     return TransferBufferToString(sb, rval);
    4206             : }
    4207             : 
    4208             : enum DecodeResult { Decode_Failure, Decode_BadUri, Decode_Success };
    4209             : 
    4210             : template <typename CharT>
    4211             : static DecodeResult
    4212           1 : Decode(StringBuffer& sb, const CharT* chars, size_t length, const bool* reservedSet)
    4213             : {
    4214          54 :     for (size_t k = 0; k < length; k++) {
    4215          53 :         char16_t c = chars[k];
    4216          53 :         if (c == '%') {
    4217           0 :             size_t start = k;
    4218           0 :             if ((k + 2) >= length)
    4219           0 :                 return Decode_BadUri;
    4220             : 
    4221           0 :             if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
    4222           0 :                 return Decode_BadUri;
    4223             : 
    4224           0 :             uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
    4225           0 :             k += 2;
    4226           0 :             if (!(B & 0x80)) {
    4227           0 :                 c = char16_t(B);
    4228             :             } else {
    4229           0 :                 int n = 1;
    4230           0 :                 while (B & (0x80 >> n))
    4231           0 :                     n++;
    4232             : 
    4233           0 :                 if (n == 1 || n > 4)
    4234           0 :                     return Decode_BadUri;
    4235             : 
    4236             :                 uint8_t octets[4];
    4237           0 :                 octets[0] = (uint8_t)B;
    4238           0 :                 if (k + 3 * (n - 1) >= length)
    4239           0 :                     return Decode_BadUri;
    4240             : 
    4241           0 :                 for (int j = 1; j < n; j++) {
    4242           0 :                     k++;
    4243           0 :                     if (chars[k] != '%')
    4244           0 :                         return Decode_BadUri;
    4245             : 
    4246           0 :                     if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
    4247           0 :                         return Decode_BadUri;
    4248             : 
    4249           0 :                     B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
    4250           0 :                     if ((B & 0xC0) != 0x80)
    4251           0 :                         return Decode_BadUri;
    4252             : 
    4253           0 :                     k += 2;
    4254           0 :                     octets[j] = char(B);
    4255             :                 }
    4256           0 :                 uint32_t v = JS::Utf8ToOneUcs4Char(octets, n);
    4257           0 :                 if (v >= unicode::NonBMPMin) {
    4258           0 :                     if (v > unicode::NonBMPMax)
    4259           0 :                         return Decode_BadUri;
    4260             : 
    4261           0 :                     char16_t H = unicode::LeadSurrogate(v);
    4262           0 :                     if (!sb.append(H))
    4263           0 :                         return Decode_Failure;
    4264           0 :                     c = unicode::TrailSurrogate(v);
    4265             :                 } else {
    4266           0 :                     c = char16_t(v);
    4267             :                 }
    4268             :             }
    4269           0 :             if (c < 128 && reservedSet && reservedSet[c]) {
    4270           0 :                 if (!sb.append(chars + start, k - start + 1))
    4271           0 :                     return Decode_Failure;
    4272             :             } else {
    4273           0 :                 if (!sb.append(c))
    4274           0 :                     return Decode_Failure;
    4275             :             }
    4276             :         } else {
    4277          53 :             if (!sb.append(c))
    4278           0 :                 return Decode_Failure;
    4279             :         }
    4280             :     }
    4281             : 
    4282           1 :     return Decode_Success;
    4283             : }
    4284             : 
    4285             : static bool
    4286           1 : Decode(JSContext* cx, HandleLinearString str, const bool* reservedSet, MutableHandleValue rval)
    4287             : {
    4288           1 :     size_t length = str->length();
    4289           1 :     if (length == 0) {
    4290           0 :         rval.setString(cx->runtime()->emptyString);
    4291           0 :         return true;
    4292             :     }
    4293             : 
    4294           2 :     StringBuffer sb(cx);
    4295             : 
    4296             :     DecodeResult res;
    4297           1 :     if (str->hasLatin1Chars()) {
    4298           2 :         AutoCheckCannotGC nogc;
    4299           1 :         res = Decode(sb, str->latin1Chars(nogc), str->length(), reservedSet);
    4300             :     } else {
    4301           0 :         AutoCheckCannotGC nogc;
    4302           0 :         res = Decode(sb, str->twoByteChars(nogc), str->length(), reservedSet);
    4303             :     }
    4304             : 
    4305           1 :     if (res == Decode_Failure)
    4306           0 :         return false;
    4307             : 
    4308           1 :     if (res == Decode_BadUri) {
    4309           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
    4310           0 :         return false;
    4311             :     }
    4312             : 
    4313           1 :     MOZ_ASSERT(res == Decode_Success);
    4314           1 :     return TransferBufferToString(sb, rval);
    4315             : }
    4316             : 
    4317             : static bool
    4318           1 : str_decodeURI(JSContext* cx, unsigned argc, Value* vp)
    4319             : {
    4320           1 :     CallArgs args = CallArgsFromVp(argc, vp);
    4321           2 :     RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
    4322           1 :     if (!str)
    4323           0 :         return false;
    4324             : 
    4325           1 :     return Decode(cx, str, js_isUriReservedPlusPound, args.rval());
    4326             : }
    4327             : 
    4328             : static bool
    4329           0 : str_decodeURI_Component(JSContext* cx, unsigned argc, Value* vp)
    4330             : {
    4331           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    4332           0 :     RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
    4333           0 :     if (!str)
    4334           0 :         return false;
    4335             : 
    4336           0 :     return Decode(cx, str, nullptr, args.rval());
    4337             : }
    4338             : 
    4339             : static bool
    4340           0 : str_encodeURI(JSContext* cx, unsigned argc, Value* vp)
    4341             : {
    4342           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    4343           0 :     RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
    4344           0 :     if (!str)
    4345           0 :         return false;
    4346             : 
    4347           0 :     return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval());
    4348             : }
    4349             : 
    4350             : static bool
    4351         510 : str_encodeURI_Component(JSContext* cx, unsigned argc, Value* vp)
    4352             : {
    4353         510 :     CallArgs args = CallArgsFromVp(argc, vp);
    4354        1020 :     RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
    4355         510 :     if (!str)
    4356           0 :         return false;
    4357             : 
    4358         510 :     return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval());
    4359             : }
    4360             : 
    4361             : bool
    4362           0 : js::EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length)
    4363             : {
    4364           0 :     EncodeResult result = Encode(sb, chars, length, js_isUriUnescaped, js_isUriReservedPlusPound);
    4365           0 :     if (result == EncodeResult::Encode_Failure)
    4366           0 :         return false;
    4367           0 :     if (result == EncodeResult::Encode_BadUri) {
    4368           0 :         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
    4369           0 :         return false;
    4370             :     }
    4371           0 :     return true;
    4372             : }
    4373             : 
    4374             : /*
    4375             :  * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
    4376             :  * least 4 bytes long.  Return the number of UTF-8 bytes of data written.
    4377             :  */
    4378             : uint32_t
    4379         270 : js::OneUcs4ToUtf8Char(uint8_t* utf8Buffer, uint32_t ucs4Char)
    4380             : {
    4381         270 :     MOZ_ASSERT(ucs4Char <= unicode::NonBMPMax);
    4382             : 
    4383         270 :     if (ucs4Char < 0x80) {
    4384         270 :         utf8Buffer[0] = uint8_t(ucs4Char);
    4385         270 :         return 1;
    4386             :     }
    4387             : 
    4388           0 :     uint32_t a = ucs4Char >> 11;
    4389           0 :     uint32_t utf8Length = 2;
    4390           0 :     while (a) {
    4391           0 :         a >>= 5;
    4392           0 :         utf8Length++;
    4393             :     }
    4394             : 
    4395           0 :     MOZ_ASSERT(utf8Length <= 4);
    4396             : 
    4397           0 :     uint32_t i = utf8Length;
    4398           0 :     while (--i) {
    4399           0 :         utf8Buffer[i] = uint8_t((ucs4Char & 0x3F) | 0x80);
    4400           0 :         ucs4Char >>= 6;
    4401             :     }
    4402             : 
    4403           0 :     utf8Buffer[0] = uint8_t(0x100 - (1 << (8 - utf8Length)) + ucs4Char);
    4404           0 :     return utf8Length;
    4405             : }
    4406             : 
    4407             : size_t
    4408           0 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, JSLinearString* str,
    4409             :                          uint32_t quote)
    4410             : {
    4411           0 :     size_t len = str->length();
    4412           0 :     AutoCheckCannotGC nogc;
    4413           0 :     return str->hasLatin1Chars()
    4414           0 :            ? PutEscapedStringImpl(buffer, bufferSize, out, str->latin1Chars(nogc), len, quote)
    4415           0 :            : PutEscapedStringImpl(buffer, bufferSize, out, str->twoByteChars(nogc), len, quote);
    4416             : }
    4417             : 
    4418             : template <typename CharT>
    4419             : size_t
    4420           0 : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const CharT* chars,
    4421             :                          size_t length, uint32_t quote)
    4422             : {
    4423             :     enum {
    4424             :         STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
    4425             :     } state;
    4426             : 
    4427           0 :     MOZ_ASSERT(quote == 0 || quote == '\'' || quote == '"');
    4428           0 :     MOZ_ASSERT_IF(!buffer, bufferSize == 0);
    4429           0 :     MOZ_ASSERT_IF(out, !buffer);
    4430             : 
    4431           0 :     if (bufferSize == 0)
    4432           0 :         buffer = nullptr;
    4433             :     else
    4434           0 :         bufferSize--;
    4435             : 
    4436           0 :     const CharT* charsEnd = chars + length;
    4437           0 :     size_t n = 0;
    4438           0 :     state = FIRST_QUOTE;
    4439           0 :     unsigned shift = 0;
    4440           0 :     unsigned hex = 0;
    4441           0 :     unsigned u = 0;
    4442           0 :     char c = 0;  /* to quell GCC warnings */
    4443             : 
    4444           0 :     for (;;) {
    4445           0 :         switch (state) {
    4446             :           case STOP:
    4447           0 :             goto stop;
    4448             :           case FIRST_QUOTE:
    4449           0 :             state = CHARS;
    4450           0 :             goto do_quote;
    4451             :           case LAST_QUOTE:
    4452           0 :             state = STOP;
    4453             :           do_quote:
    4454           0 :             if (quote == 0)
    4455           0 :                 continue;
    4456           0 :             c = (char)quote;
    4457           0 :             break;
    4458             :           case CHARS:
    4459           0 :             if (chars == charsEnd) {
    4460           0 :                 state = LAST_QUOTE;
    4461           0 :                 continue;
    4462             :             }
    4463           0 :             u = *chars++;
    4464           0 :             if (u < ' ') {
    4465           0 :                 if (u != 0) {
    4466           0 :                     const char* escape = strchr(js_EscapeMap, (int)u);
    4467           0 :                     if (escape) {
    4468           0 :                         u = escape[1];
    4469           0 :                         goto do_escape;
    4470             :                     }
    4471             :                 }
    4472           0 :                 goto do_hex_escape;
    4473             :             }
    4474           0 :             if (u < 127) {
    4475           0 :                 if (u == quote || u == '\\')
    4476             :                     goto do_escape;
    4477           0 :                 c = (char)u;
    4478           0 :             } else if (u < 0x100) {
    4479           0 :                 goto do_hex_escape;
    4480             :             } else {
    4481           0 :                 shift = 16;
    4482           0 :                 hex = u;
    4483           0 :                 u = 'u';
    4484           0 :                 goto do_escape;
    4485             :             }
    4486           0 :             break;
    4487             :           do_hex_escape:
    4488           0 :             shift = 8;
    4489           0 :             hex = u;
    4490           0 :             u = 'x';
    4491             :           do_escape:
    4492           0 :             c = '\\';
    4493           0 :             state = ESCAPE_START;
    4494           0 :             break;
    4495             :           case ESCAPE_START:
    4496           0 :             MOZ_ASSERT(' ' <= u && u < 127);
    4497           0 :             c = (char)u;
    4498           0 :             state = ESCAPE_MORE;
    4499           0 :             break;
    4500             :           case ESCAPE_MORE:
    4501           0 :             if (shift == 0) {
    4502           0 :                 state = CHARS;
    4503           0 :                 continue;
    4504             :             }
    4505           0 :             shift -= 4;
    4506           0 :             u = 0xF & (hex >> shift);
    4507           0 :             c = (char)(u + (u < 10 ? '0' : 'A' - 10));
    4508           0 :             break;
    4509             :         }
    4510           0 :         if (buffer) {
    4511           0 :             MOZ_ASSERT(n <= bufferSize);
    4512           0 :             if (n != bufferSize) {
    4513           0 :                 buffer[n] = c;
    4514             :             } else {
    4515           0 :                 buffer[n] = '\0';
    4516           0 :                 buffer = nullptr;
    4517             :             }
    4518           0 :         } else if (out) {
    4519           0 :             if (!out->put(&c, 1))
    4520           0 :                 return size_t(-1);
    4521             :         }
    4522           0 :         n++;
    4523             :     }
    4524             :   stop:
    4525           0 :     if (buffer)
    4526           0 :         buffer[n] = '\0';
    4527           0 :     return n;
    4528             : }
    4529             : 
    4530             : template size_t
    4531             : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const Latin1Char* chars,
    4532             :                          size_t length, uint32_t quote);
    4533             : 
    4534             : template size_t
    4535             : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char* chars,
    4536             :                          size_t length, uint32_t quote);
    4537             : 
    4538             : template size_t
    4539             : js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char16_t* chars,
    4540             :                          size_t length, uint32_t quote);
    4541             : 
    4542             : template size_t
    4543             : js::PutEscapedString(char* buffer, size_t bufferSize, const Latin1Char* chars, size_t length,
    4544             :                      uint32_t quote);
    4545             : 
    4546             : template size_t
    4547             : js::PutEscapedString(char* buffer, size_t bufferSize, const char16_t* chars, size_t length,
    4548             :                      uint32_t quote);
    4549             : 
    4550             : static bool
    4551           8 : FlatStringMatchHelper(JSContext* cx, HandleString str, HandleString pattern, bool* isFlat, int32_t* match)
    4552             : {
    4553          16 :     RootedLinearString linearPattern(cx, pattern->ensureLinear(cx));
    4554           8 :     if (!linearPattern)
    4555           0 :         return false;
    4556             : 
    4557             :     static const size_t MAX_FLAT_PAT_LEN = 256;
    4558           8 :     if (linearPattern->length() > MAX_FLAT_PAT_LEN || StringHasRegExpMetaChars(linearPattern)) {
    4559           0 :         *isFlat = false;
    4560           0 :         return true;
    4561             :     }
    4562             : 
    4563           8 :     *isFlat = true;
    4564           8 :     if (str->isRope()) {
    4565           0 :         if (!RopeMatch(cx, &str->asRope(), linearPattern, match))
    4566           0 :             return false;
    4567             :     } else {
    4568           8 :         *match = StringMatch(&str->asLinear(), linearPattern);
    4569             :     }
    4570             : 
    4571           8 :     return true;
    4572             : }
    4573             : 
    4574             : static bool
    4575           8 : BuildFlatMatchArray(JSContext* cx, HandleString str, HandleString pattern, int32_t match,
    4576             :                     MutableHandleValue rval)
    4577             : {
    4578           8 :     if (match < 0) {
    4579           7 :         rval.setNull();
    4580           7 :         return true;
    4581             :     }
    4582             : 
    4583             :     /* Get the templateObject that defines the shape and type of the output object */
    4584           1 :     JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
    4585           1 :     if (!templateObject)
    4586           0 :         return false;
    4587             : 
    4588           2 :     RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, 1, templateObject));
    4589           1 :     if (!arr)
    4590           0 :         return false;
    4591             : 
    4592             :     /* Store a Value for each pair. */
    4593           1 :     arr->setDenseInitializedLength(1);
    4594           1 :     arr->initDenseElement(0, StringValue(pattern));
    4595             : 
    4596             :     /* Set the |index| property. (TemplateObject positions it in slot 0) */
    4597           1 :     arr->setSlot(0, Int32Value(match));
    4598             : 
    4599             :     /* Set the |input| property. (TemplateObject positions it in slot 1) */
    4600           1 :     arr->setSlot(1, StringValue(str));
    4601             : 
    4602             : #ifdef DEBUG
    4603           2 :     RootedValue test(cx);
    4604           2 :     RootedId id(cx, NameToId(cx->names().index));
    4605           1 :     if (!NativeGetProperty(cx, arr, id, &test))
    4606           0 :         return false;
    4607           1 :     MOZ_ASSERT(test == arr->getSlot(0));
    4608           1 :     id = NameToId(cx->names().input);
    4609           1 :     if (!NativeGetProperty(cx, arr, id, &test))
    4610           0 :         return false;
    4611           1 :     MOZ_ASSERT(test == arr->getSlot(1));
    4612             : #endif
    4613             : 
    4614           1 :     rval.setObject(*arr);
    4615           1 :     return true;
    4616             : }
    4617             : 
    4618             : #ifdef DEBUG
    4619             : static bool
    4620           8 : CallIsStringOptimizable(JSContext* cx, const char* name, bool* result)
    4621             : {
    4622           8 :     JSAtom* atom = Atomize(cx, name, strlen(name));
    4623           8 :     if (!atom)
    4624           0 :         return false;
    4625          16 :     RootedPropertyName propName(cx, atom->asPropertyName());
    4626             : 
    4627          16 :     RootedValue funcVal(cx);
    4628           8 :     if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), propName, propName, 0, &funcVal))
    4629           0 :         return false;
    4630             : 
    4631          16 :     FixedInvokeArgs<0> args(cx);
    4632             : 
    4633          16 :     RootedValue rval(cx);
    4634           8 :     if (!Call(cx, funcVal, UndefinedHandleValue, args, &rval))
    4635           0 :         return false;
    4636             : 
    4637           8 :     *result = rval.toBoolean();
    4638           8 :     return true;
    4639             : }
    4640             : #endif
    4641             : 
    4642             : bool
    4643           8 : js::FlatStringMatch(JSContext* cx, unsigned argc, Value* vp)
    4644             : {
    4645           8 :     CallArgs args = CallArgsFromVp(argc, vp);
    4646           8 :     MOZ_ASSERT(args.length() == 2);
    4647           8 :     MOZ_ASSERT(args[0].isString());
    4648           8 :     MOZ_ASSERT(args[1].isString());
    4649             : #ifdef DEBUG
    4650           8 :     bool isOptimizable = false;
    4651           8 :     if (!CallIsStringOptimizable(cx, "IsStringMatchOptimizable", &isOptimizable))
    4652           0 :         return false;
    4653           8 :     MOZ_ASSERT(isOptimizable);
    4654             : #endif
    4655             : 
    4656          16 :     RootedString str(cx,args[0].toString());
    4657          16 :     RootedString pattern(cx, args[1].toString());
    4658             : 
    4659           8 :     bool isFlat = false;
    4660           8 :     int32_t match = 0;
    4661           8 :     if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
    4662           0 :         return false;
    4663             : 
    4664           8 :     if (!isFlat) {
    4665           0 :         args.rval().setUndefined();
    4666           0 :         return true;
    4667             :     }
    4668             : 
    4669           8 :     return BuildFlatMatchArray(cx, str, pattern, match, args.rval());
    4670             : }
    4671             : 
    4672             : bool
    4673           0 : js::FlatStringSearch(JSContext* cx, unsigned argc, Value* vp)
    4674             : {
    4675           0 :     CallArgs args = CallArgsFromVp(argc, vp);
    4676           0 :     MOZ_ASSERT(args.length() == 2);
    4677           0 :     MOZ_ASSERT(args[0].isString());
    4678           0 :     MOZ_ASSERT(args[1].isString());
    4679             : #ifdef DEBUG
    4680           0 :     bool isOptimizable = false;
    4681           0 :     if (!CallIsStringOptimizable(cx, "IsStringSearchOptimizable", &isOptimizable))
    4682           0 :         return false;
    4683           0 :     MOZ_ASSERT(isOptimizable);
    4684             : #endif
    4685             : 
    4686           0 :     RootedString str(cx,args[0].toString());
    4687           0 :     RootedString pattern(cx, args[1].toString());
    4688             : 
    4689           0 :     bool isFlat = false;
    4690           0 :     int32_t match = 0;
    4691           0 :     if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
    4692           0 :         return false;
    4693             : 
    4694           0 :     if (!isFlat) {
    4695           0 :         args.rval().setInt32(-2);
    4696           0 :         return true;
    4697             :     }
    4698             : 
    4699           0 :     args.rval().setInt32(match);
    4700           0 :     return true;
    4701             : }

Generated by: LCOV version 1.13