js/src/jsstr.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/jsstr.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,4943 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +/*
    1.11 + * JS string type implementation.
    1.12 + *
    1.13 + * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these
    1.14 + * native methods store strings (possibly newborn) converted from their 'this'
    1.15 + * parameter and arguments on the stack: 'this' conversions at argv[-1], arg
    1.16 + * conversions at their index (argv[0], argv[1]).  This is a legitimate method
    1.17 + * of rooting things that might lose their newborn root due to subsequent GC
    1.18 + * allocations in the same native method.
    1.19 + */
    1.20 +
    1.21 +#include "jsstr.h"
    1.22 +
    1.23 +#include "mozilla/Attributes.h"
    1.24 +#include "mozilla/Casting.h"
    1.25 +#include "mozilla/CheckedInt.h"
    1.26 +#include "mozilla/FloatingPoint.h"
    1.27 +#include "mozilla/PodOperations.h"
    1.28 +
    1.29 +#include <ctype.h>
    1.30 +#include <string.h>
    1.31 +
    1.32 +#include "jsapi.h"
    1.33 +#include "jsarray.h"
    1.34 +#include "jsatom.h"
    1.35 +#include "jsbool.h"
    1.36 +#include "jscntxt.h"
    1.37 +#include "jsgc.h"
    1.38 +#include "jsnum.h"
    1.39 +#include "jsobj.h"
    1.40 +#include "jsopcode.h"
    1.41 +#include "jstypes.h"
    1.42 +#include "jsutil.h"
    1.43 +
    1.44 +#include "builtin/Intl.h"
    1.45 +#include "builtin/RegExp.h"
    1.46 +#if ENABLE_INTL_API
    1.47 +#include "unicode/unorm.h"
    1.48 +#endif
    1.49 +#include "vm/GlobalObject.h"
    1.50 +#include "vm/Interpreter.h"
    1.51 +#include "vm/NumericConversions.h"
    1.52 +#include "vm/Opcodes.h"
    1.53 +#include "vm/RegExpObject.h"
    1.54 +#include "vm/RegExpStatics.h"
    1.55 +#include "vm/ScopeObject.h"
    1.56 +#include "vm/StringBuffer.h"
    1.57 +
    1.58 +#include "jsinferinlines.h"
    1.59 +
    1.60 +#include "vm/Interpreter-inl.h"
    1.61 +#include "vm/String-inl.h"
    1.62 +#include "vm/StringObject-inl.h"
    1.63 +
    1.64 +using namespace js;
    1.65 +using namespace js::gc;
    1.66 +using namespace js::types;
    1.67 +using namespace js::unicode;
    1.68 +
    1.69 +using mozilla::CheckedInt;
    1.70 +using mozilla::IsNaN;
    1.71 +using mozilla::IsNegativeZero;
    1.72 +using mozilla::PodCopy;
    1.73 +using mozilla::PodEqual;
    1.74 +using mozilla::SafeCast;
    1.75 +
    1.76 +typedef Handle<JSLinearString*> HandleLinearString;
    1.77 +
    1.78 +static JSLinearString *
    1.79 +ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno)
    1.80 +{
    1.81 +    if (argno >= args.length())
    1.82 +        return cx->names().undefined;
    1.83 +
    1.84 +    JSString *str = ToString<CanGC>(cx, args[argno]);
    1.85 +    if (!str)
    1.86 +        return nullptr;
    1.87 +
    1.88 +    args[argno].setString(str);
    1.89 +    return str->ensureLinear(cx);
    1.90 +}
    1.91 +
    1.92 +/*
    1.93 + * Forward declarations for URI encode/decode and helper routines
    1.94 + */
    1.95 +static bool
    1.96 +str_decodeURI(JSContext *cx, unsigned argc, Value *vp);
    1.97 +
    1.98 +static bool
    1.99 +str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp);
   1.100 +
   1.101 +static bool
   1.102 +str_encodeURI(JSContext *cx, unsigned argc, Value *vp);
   1.103 +
   1.104 +static bool
   1.105 +str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp);
   1.106 +
   1.107 +/*
   1.108 + * Global string methods
   1.109 + */
   1.110 +
   1.111 +
   1.112 +/* ES5 B.2.1 */
   1.113 +static bool
   1.114 +str_escape(JSContext *cx, unsigned argc, Value *vp)
   1.115 +{
   1.116 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.117 +
   1.118 +    static const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
   1.119 +                                  '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
   1.120 +
   1.121 +    JSLinearString *str = ArgToRootedString(cx, args, 0);
   1.122 +    if (!str)
   1.123 +        return false;
   1.124 +
   1.125 +    size_t length = str->length();
   1.126 +    const jschar *chars = str->chars();
   1.127 +
   1.128 +    static const uint8_t shouldPassThrough[256] = {
   1.129 +         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   1.130 +         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   1.131 +         0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,       /*    !"#$%&'()*+,-./  */
   1.132 +         1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,       /*   0123456789:;<=>?  */
   1.133 +         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,       /*   @ABCDEFGHIJKLMNO  */
   1.134 +         1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,       /*   PQRSTUVWXYZ[\]^_  */
   1.135 +         0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,       /*   `abcdefghijklmno  */
   1.136 +         1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,     /*   pqrstuvwxyz{\}~  DEL */
   1.137 +    };
   1.138 +
   1.139 +    /* In step 7, exactly 69 characters should pass through unencoded. */
   1.140 +#ifdef DEBUG
   1.141 +    size_t count = 0;
   1.142 +    for (size_t i = 0; i < sizeof(shouldPassThrough); i++) {
   1.143 +        if (shouldPassThrough[i]) {
   1.144 +            count++;
   1.145 +        }
   1.146 +    }
   1.147 +    JS_ASSERT(count == 69);
   1.148 +#endif
   1.149 +
   1.150 +
   1.151 +    /* Take a first pass and see how big the result string will need to be. */
   1.152 +    size_t newlength = length;
   1.153 +    for (size_t i = 0; i < length; i++) {
   1.154 +        jschar ch = chars[i];
   1.155 +        if (ch < 128 && shouldPassThrough[ch])
   1.156 +            continue;
   1.157 +
   1.158 +        /* The character will be encoded as %XX or %uXXXX. */
   1.159 +        newlength += (ch < 256) ? 2 : 5;
   1.160 +
   1.161 +        /*
   1.162 +         * This overflow test works because newlength is incremented by at
   1.163 +         * most 5 on each iteration.
   1.164 +         */
   1.165 +        if (newlength < length) {
   1.166 +            js_ReportAllocationOverflow(cx);
   1.167 +            return false;
   1.168 +        }
   1.169 +    }
   1.170 +
   1.171 +    if (newlength >= ~(size_t)0 / sizeof(jschar)) {
   1.172 +        js_ReportAllocationOverflow(cx);
   1.173 +        return false;
   1.174 +    }
   1.175 +
   1.176 +    jschar *newchars = cx->pod_malloc<jschar>(newlength + 1);
   1.177 +    if (!newchars)
   1.178 +        return false;
   1.179 +    size_t i, ni;
   1.180 +    for (i = 0, ni = 0; i < length; i++) {
   1.181 +        jschar ch = chars[i];
   1.182 +        if (ch < 128 && shouldPassThrough[ch]) {
   1.183 +            newchars[ni++] = ch;
   1.184 +        } else if (ch < 256) {
   1.185 +            newchars[ni++] = '%';
   1.186 +            newchars[ni++] = digits[ch >> 4];
   1.187 +            newchars[ni++] = digits[ch & 0xF];
   1.188 +        } else {
   1.189 +            newchars[ni++] = '%';
   1.190 +            newchars[ni++] = 'u';
   1.191 +            newchars[ni++] = digits[ch >> 12];
   1.192 +            newchars[ni++] = digits[(ch & 0xF00) >> 8];
   1.193 +            newchars[ni++] = digits[(ch & 0xF0) >> 4];
   1.194 +            newchars[ni++] = digits[ch & 0xF];
   1.195 +        }
   1.196 +    }
   1.197 +    JS_ASSERT(ni == newlength);
   1.198 +    newchars[newlength] = 0;
   1.199 +
   1.200 +    JSString *retstr = js_NewString<CanGC>(cx, newchars, newlength);
   1.201 +    if (!retstr) {
   1.202 +        js_free(newchars);
   1.203 +        return false;
   1.204 +    }
   1.205 +
   1.206 +    args.rval().setString(retstr);
   1.207 +    return true;
   1.208 +}
   1.209 +
   1.210 +static inline bool
   1.211 +Unhex4(const jschar *chars, jschar *result)
   1.212 +{
   1.213 +    jschar a = chars[0],
   1.214 +           b = chars[1],
   1.215 +           c = chars[2],
   1.216 +           d = chars[3];
   1.217 +
   1.218 +    if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d)))
   1.219 +        return false;
   1.220 +
   1.221 +    *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d);
   1.222 +    return true;
   1.223 +}
   1.224 +
   1.225 +static inline bool
   1.226 +Unhex2(const jschar *chars, jschar *result)
   1.227 +{
   1.228 +    jschar a = chars[0],
   1.229 +           b = chars[1];
   1.230 +
   1.231 +    if (!(JS7_ISHEX(a) && JS7_ISHEX(b)))
   1.232 +        return false;
   1.233 +
   1.234 +    *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b);
   1.235 +    return true;
   1.236 +}
   1.237 +
   1.238 +/* ES5 B.2.2 */
   1.239 +static bool
   1.240 +str_unescape(JSContext *cx, unsigned argc, Value *vp)
   1.241 +{
   1.242 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.243 +
   1.244 +    /* Step 1. */
   1.245 +    JSLinearString *str = ArgToRootedString(cx, args, 0);
   1.246 +    if (!str)
   1.247 +        return false;
   1.248 +
   1.249 +    /*
   1.250 +     * NB: use signed integers for length/index to allow simple length
   1.251 +     * comparisons without unsigned-underflow hazards.
   1.252 +     */
   1.253 +    JS_STATIC_ASSERT(JSString::MAX_LENGTH <= INT_MAX);
   1.254 +
   1.255 +    /* Step 2. */
   1.256 +    int length = str->length();
   1.257 +    const jschar *chars = str->chars();
   1.258 +
   1.259 +    /* Step 3. */
   1.260 +    StringBuffer sb(cx);
   1.261 +
   1.262 +    /*
   1.263 +     * Note that the spec algorithm has been optimized to avoid building
   1.264 +     * a string in the case where no escapes are present.
   1.265 +     */
   1.266 +
   1.267 +    /* Step 4. */
   1.268 +    int k = 0;
   1.269 +    bool building = false;
   1.270 +
   1.271 +    while (true) {
   1.272 +        /* Step 5. */
   1.273 +        if (k == length) {
   1.274 +            JSLinearString *result;
   1.275 +            if (building) {
   1.276 +                result = sb.finishString();
   1.277 +                if (!result)
   1.278 +                    return false;
   1.279 +            } else {
   1.280 +                result = str;
   1.281 +            }
   1.282 +
   1.283 +            args.rval().setString(result);
   1.284 +            return true;
   1.285 +        }
   1.286 +
   1.287 +        /* Step 6. */
   1.288 +        jschar c = chars[k];
   1.289 +
   1.290 +        /* Step 7. */
   1.291 +        if (c != '%')
   1.292 +            goto step_18;
   1.293 +
   1.294 +        /* Step 8. */
   1.295 +        if (k > length - 6)
   1.296 +            goto step_14;
   1.297 +
   1.298 +        /* Step 9. */
   1.299 +        if (chars[k + 1] != 'u')
   1.300 +            goto step_14;
   1.301 +
   1.302 +#define ENSURE_BUILDING                             \
   1.303 +    JS_BEGIN_MACRO                                  \
   1.304 +        if (!building) {                            \
   1.305 +            building = true;                        \
   1.306 +            if (!sb.reserve(length))                \
   1.307 +                return false;                       \
   1.308 +            sb.infallibleAppend(chars, chars + k);  \
   1.309 +        }                                           \
   1.310 +    JS_END_MACRO
   1.311 +
   1.312 +        /* Step 10-13. */
   1.313 +        if (Unhex4(&chars[k + 2], &c)) {
   1.314 +            ENSURE_BUILDING;
   1.315 +            k += 5;
   1.316 +            goto step_18;
   1.317 +        }
   1.318 +
   1.319 +      step_14:
   1.320 +        /* Step 14. */
   1.321 +        if (k > length - 3)
   1.322 +            goto step_18;
   1.323 +
   1.324 +        /* Step 15-17. */
   1.325 +        if (Unhex2(&chars[k + 1], &c)) {
   1.326 +            ENSURE_BUILDING;
   1.327 +            k += 2;
   1.328 +        }
   1.329 +
   1.330 +      step_18:
   1.331 +        if (building)
   1.332 +            sb.infallibleAppend(c);
   1.333 +
   1.334 +        /* Step 19. */
   1.335 +        k += 1;
   1.336 +    }
   1.337 +#undef ENSURE_BUILDING
   1.338 +}
   1.339 +
   1.340 +#if JS_HAS_UNEVAL
   1.341 +static bool
   1.342 +str_uneval(JSContext *cx, unsigned argc, Value *vp)
   1.343 +{
   1.344 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.345 +    JSString *str = ValueToSource(cx, args.get(0));
   1.346 +    if (!str)
   1.347 +        return false;
   1.348 +
   1.349 +    args.rval().setString(str);
   1.350 +    return true;
   1.351 +}
   1.352 +#endif
   1.353 +
   1.354 +static const JSFunctionSpec string_functions[] = {
   1.355 +    JS_FN(js_escape_str,             str_escape,                1,0),
   1.356 +    JS_FN(js_unescape_str,           str_unescape,              1,0),
   1.357 +#if JS_HAS_UNEVAL
   1.358 +    JS_FN(js_uneval_str,             str_uneval,                1,0),
   1.359 +#endif
   1.360 +    JS_FN(js_decodeURI_str,          str_decodeURI,             1,0),
   1.361 +    JS_FN(js_encodeURI_str,          str_encodeURI,             1,0),
   1.362 +    JS_FN(js_decodeURIComponent_str, str_decodeURI_Component,   1,0),
   1.363 +    JS_FN(js_encodeURIComponent_str, str_encodeURI_Component,   1,0),
   1.364 +
   1.365 +    JS_FS_END
   1.366 +};
   1.367 +
   1.368 +const jschar      js_empty_ucstr[]  = {0};
   1.369 +const JSSubString js_EmptySubString = {0, js_empty_ucstr};
   1.370 +
   1.371 +static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
   1.372 +
   1.373 +static bool
   1.374 +str_enumerate(JSContext *cx, HandleObject obj)
   1.375 +{
   1.376 +    RootedString str(cx, obj->as<StringObject>().unbox());
   1.377 +    RootedValue value(cx);
   1.378 +    for (size_t i = 0, length = str->length(); i < length; i++) {
   1.379 +        JSString *str1 = js_NewDependentString(cx, str, i, 1);
   1.380 +        if (!str1)
   1.381 +            return false;
   1.382 +        value.setString(str1);
   1.383 +        if (!JSObject::defineElement(cx, obj, i, value,
   1.384 +                                     JS_PropertyStub, JS_StrictPropertyStub,
   1.385 +                                     STRING_ELEMENT_ATTRS))
   1.386 +        {
   1.387 +            return false;
   1.388 +        }
   1.389 +    }
   1.390 +
   1.391 +    return true;
   1.392 +}
   1.393 +
   1.394 +bool
   1.395 +js::str_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp)
   1.396 +{
   1.397 +    if (!JSID_IS_INT(id))
   1.398 +        return true;
   1.399 +
   1.400 +    RootedString str(cx, obj->as<StringObject>().unbox());
   1.401 +
   1.402 +    int32_t slot = JSID_TO_INT(id);
   1.403 +    if ((size_t)slot < str->length()) {
   1.404 +        JSString *str1 = cx->staticStrings().getUnitStringForElement(cx, str, size_t(slot));
   1.405 +        if (!str1)
   1.406 +            return false;
   1.407 +        RootedValue value(cx, StringValue(str1));
   1.408 +        if (!JSObject::defineElement(cx, obj, uint32_t(slot), value, nullptr, nullptr,
   1.409 +                                     STRING_ELEMENT_ATTRS))
   1.410 +        {
   1.411 +            return false;
   1.412 +        }
   1.413 +        objp.set(obj);
   1.414 +    }
   1.415 +    return true;
   1.416 +}
   1.417 +
   1.418 +const Class StringObject::class_ = {
   1.419 +    js_String_str,
   1.420 +    JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) |
   1.421 +    JSCLASS_NEW_RESOLVE | JSCLASS_HAS_CACHED_PROTO(JSProto_String),
   1.422 +    JS_PropertyStub,         /* addProperty */
   1.423 +    JS_DeletePropertyStub,   /* delProperty */
   1.424 +    JS_PropertyStub,         /* getProperty */
   1.425 +    JS_StrictPropertyStub,   /* setProperty */
   1.426 +    str_enumerate,
   1.427 +    (JSResolveOp)str_resolve,
   1.428 +    JS_ConvertStub
   1.429 +};
   1.430 +
   1.431 +/*
   1.432 + * Returns a JSString * for the |this| value associated with 'call', or throws
   1.433 + * a TypeError if |this| is null or undefined.  This algorithm is the same as
   1.434 + * calling CheckObjectCoercible(this), then returning ToString(this), as all
   1.435 + * String.prototype.* methods do (other than toString and valueOf).
   1.436 + */
   1.437 +static MOZ_ALWAYS_INLINE JSString *
   1.438 +ThisToStringForStringProto(JSContext *cx, CallReceiver call)
   1.439 +{
   1.440 +    JS_CHECK_RECURSION(cx, return nullptr);
   1.441 +
   1.442 +    if (call.thisv().isString())
   1.443 +        return call.thisv().toString();
   1.444 +
   1.445 +    if (call.thisv().isObject()) {
   1.446 +        RootedObject obj(cx, &call.thisv().toObject());
   1.447 +        if (obj->is<StringObject>()) {
   1.448 +            Rooted<jsid> id(cx, NameToId(cx->names().toString));
   1.449 +            if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) {
   1.450 +                JSString *str = obj->as<StringObject>().unbox();
   1.451 +                call.setThis(StringValue(str));
   1.452 +                return str;
   1.453 +            }
   1.454 +        }
   1.455 +    } else if (call.thisv().isNullOrUndefined()) {
   1.456 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
   1.457 +                             call.thisv().isNull() ? "null" : "undefined", "object");
   1.458 +        return nullptr;
   1.459 +    }
   1.460 +
   1.461 +    JSString *str = ToStringSlow<CanGC>(cx, call.thisv());
   1.462 +    if (!str)
   1.463 +        return nullptr;
   1.464 +
   1.465 +    call.setThis(StringValue(str));
   1.466 +    return str;
   1.467 +}
   1.468 +
   1.469 +MOZ_ALWAYS_INLINE bool
   1.470 +IsString(HandleValue v)
   1.471 +{
   1.472 +    return v.isString() || (v.isObject() && v.toObject().is<StringObject>());
   1.473 +}
   1.474 +
   1.475 +#if JS_HAS_TOSOURCE
   1.476 +
   1.477 +/*
   1.478 + * String.prototype.quote is generic (as are most string methods), unlike
   1.479 + * toSource, toString, and valueOf.
   1.480 + */
   1.481 +static bool
   1.482 +str_quote(JSContext *cx, unsigned argc, Value *vp)
   1.483 +{
   1.484 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.485 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
   1.486 +    if (!str)
   1.487 +        return false;
   1.488 +    str = js_QuoteString(cx, str, '"');
   1.489 +    if (!str)
   1.490 +        return false;
   1.491 +    args.rval().setString(str);
   1.492 +    return true;
   1.493 +}
   1.494 +
   1.495 +MOZ_ALWAYS_INLINE bool
   1.496 +str_toSource_impl(JSContext *cx, CallArgs args)
   1.497 +{
   1.498 +    JS_ASSERT(IsString(args.thisv()));
   1.499 +
   1.500 +    Rooted<JSString*> str(cx, ToString<CanGC>(cx, args.thisv()));
   1.501 +    if (!str)
   1.502 +        return false;
   1.503 +
   1.504 +    str = js_QuoteString(cx, str, '"');
   1.505 +    if (!str)
   1.506 +        return false;
   1.507 +
   1.508 +    StringBuffer sb(cx);
   1.509 +    if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))"))
   1.510 +        return false;
   1.511 +
   1.512 +    str = sb.finishString();
   1.513 +    if (!str)
   1.514 +        return false;
   1.515 +    args.rval().setString(str);
   1.516 +    return true;
   1.517 +}
   1.518 +
   1.519 +static bool
   1.520 +str_toSource(JSContext *cx, unsigned argc, Value *vp)
   1.521 +{
   1.522 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.523 +    return CallNonGenericMethod<IsString, str_toSource_impl>(cx, args);
   1.524 +}
   1.525 +
   1.526 +#endif /* JS_HAS_TOSOURCE */
   1.527 +
   1.528 +MOZ_ALWAYS_INLINE bool
   1.529 +str_toString_impl(JSContext *cx, CallArgs args)
   1.530 +{
   1.531 +    JS_ASSERT(IsString(args.thisv()));
   1.532 +
   1.533 +    args.rval().setString(args.thisv().isString()
   1.534 +                              ? args.thisv().toString()
   1.535 +                              : args.thisv().toObject().as<StringObject>().unbox());
   1.536 +    return true;
   1.537 +}
   1.538 +
   1.539 +bool
   1.540 +js_str_toString(JSContext *cx, unsigned argc, Value *vp)
   1.541 +{
   1.542 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.543 +    return CallNonGenericMethod<IsString, str_toString_impl>(cx, args);
   1.544 +}
   1.545 +
   1.546 +/*
   1.547 + * Java-like string native methods.
   1.548 + */
   1.549 +
   1.550 +static MOZ_ALWAYS_INLINE bool
   1.551 +ValueToIntegerRange(JSContext *cx, HandleValue v, int32_t *out)
   1.552 +{
   1.553 +    if (v.isInt32()) {
   1.554 +        *out = v.toInt32();
   1.555 +    } else {
   1.556 +        double d;
   1.557 +        if (!ToInteger(cx, v, &d))
   1.558 +            return false;
   1.559 +        if (d > INT32_MAX)
   1.560 +            *out = INT32_MAX;
   1.561 +        else if (d < INT32_MIN)
   1.562 +            *out = INT32_MIN;
   1.563 +        else
   1.564 +            *out = int32_t(d);
   1.565 +    }
   1.566 +
   1.567 +    return true;
   1.568 +}
   1.569 +
   1.570 +static JSString *
   1.571 +DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len)
   1.572 +{
   1.573 +    /*
   1.574 +     * Optimization for one level deep ropes.
   1.575 +     * This is common for the following pattern:
   1.576 +     *
   1.577 +     * while() {
   1.578 +     *   text = text.substr(0, x) + "bla" + text.substr(x)
   1.579 +     *   test.charCodeAt(x + 1)
   1.580 +     * }
   1.581 +     */
   1.582 +    if (str->isRope()) {
   1.583 +        JSRope *rope = &str->asRope();
   1.584 +
   1.585 +        /* Substring is totally in leftChild of rope. */
   1.586 +        if (begin + len <= rope->leftChild()->length()) {
   1.587 +            str = rope->leftChild();
   1.588 +            return js_NewDependentString(cx, str, begin, len);
   1.589 +        }
   1.590 +
   1.591 +        /* Substring is totally in rightChild of rope. */
   1.592 +        if (begin >= rope->leftChild()->length()) {
   1.593 +            str = rope->rightChild();
   1.594 +            begin -= rope->leftChild()->length();
   1.595 +            return js_NewDependentString(cx, str, begin, len);
   1.596 +        }
   1.597 +
   1.598 +        /*
   1.599 +         * Requested substring is partly in the left and partly in right child.
   1.600 +         * Create a rope of substrings for both childs.
   1.601 +         */
   1.602 +        JS_ASSERT (begin < rope->leftChild()->length() &&
   1.603 +                   begin + len > rope->leftChild()->length());
   1.604 +
   1.605 +        size_t lhsLength = rope->leftChild()->length() - begin;
   1.606 +        size_t rhsLength = begin + len - rope->leftChild()->length();
   1.607 +
   1.608 +        Rooted<JSRope *> ropeRoot(cx, rope);
   1.609 +        RootedString lhs(cx, js_NewDependentString(cx, ropeRoot->leftChild(),
   1.610 +                                                   begin, lhsLength));
   1.611 +        if (!lhs)
   1.612 +            return nullptr;
   1.613 +
   1.614 +        RootedString rhs(cx, js_NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength));
   1.615 +        if (!rhs)
   1.616 +            return nullptr;
   1.617 +
   1.618 +        return JSRope::new_<CanGC>(cx, lhs, rhs, len);
   1.619 +    }
   1.620 +
   1.621 +    return js_NewDependentString(cx, str, begin, len);
   1.622 +}
   1.623 +
   1.624 +static bool
   1.625 +str_substring(JSContext *cx, unsigned argc, Value *vp)
   1.626 +{
   1.627 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.628 +
   1.629 +    JSString *str = ThisToStringForStringProto(cx, args);
   1.630 +    if (!str)
   1.631 +        return false;
   1.632 +
   1.633 +    int32_t length, begin, end;
   1.634 +    if (args.length() > 0) {
   1.635 +        end = length = int32_t(str->length());
   1.636 +
   1.637 +        if (args[0].isInt32()) {
   1.638 +            begin = args[0].toInt32();
   1.639 +        } else {
   1.640 +            RootedString strRoot(cx, str);
   1.641 +            if (!ValueToIntegerRange(cx, args[0], &begin))
   1.642 +                return false;
   1.643 +            str = strRoot;
   1.644 +        }
   1.645 +
   1.646 +        if (begin < 0)
   1.647 +            begin = 0;
   1.648 +        else if (begin > length)
   1.649 +            begin = length;
   1.650 +
   1.651 +        if (args.hasDefined(1)) {
   1.652 +            if (args[1].isInt32()) {
   1.653 +                end = args[1].toInt32();
   1.654 +            } else {
   1.655 +                RootedString strRoot(cx, str);
   1.656 +                if (!ValueToIntegerRange(cx, args[1], &end))
   1.657 +                    return false;
   1.658 +                str = strRoot;
   1.659 +            }
   1.660 +
   1.661 +            if (end > length) {
   1.662 +                end = length;
   1.663 +            } else {
   1.664 +                if (end < 0)
   1.665 +                    end = 0;
   1.666 +                if (end < begin) {
   1.667 +                    int32_t tmp = begin;
   1.668 +                    begin = end;
   1.669 +                    end = tmp;
   1.670 +                }
   1.671 +            }
   1.672 +        }
   1.673 +
   1.674 +        str = DoSubstr(cx, str, size_t(begin), size_t(end - begin));
   1.675 +        if (!str)
   1.676 +            return false;
   1.677 +    }
   1.678 +
   1.679 +    args.rval().setString(str);
   1.680 +    return true;
   1.681 +}
   1.682 +
   1.683 +JSString* JS_FASTCALL
   1.684 +js_toLowerCase(JSContext *cx, JSString *str)
   1.685 +{
   1.686 +    size_t n = str->length();
   1.687 +    const jschar *s = str->getChars(cx);
   1.688 +    if (!s)
   1.689 +        return nullptr;
   1.690 +
   1.691 +    jschar *news = cx->pod_malloc<jschar>(n + 1);
   1.692 +    if (!news)
   1.693 +        return nullptr;
   1.694 +    for (size_t i = 0; i < n; i++)
   1.695 +        news[i] = unicode::ToLowerCase(s[i]);
   1.696 +    news[n] = 0;
   1.697 +    str = js_NewString<CanGC>(cx, news, n);
   1.698 +    if (!str) {
   1.699 +        js_free(news);
   1.700 +        return nullptr;
   1.701 +    }
   1.702 +    return str;
   1.703 +}
   1.704 +
   1.705 +static inline bool
   1.706 +ToLowerCaseHelper(JSContext *cx, CallReceiver call)
   1.707 +{
   1.708 +    RootedString str(cx, ThisToStringForStringProto(cx, call));
   1.709 +    if (!str)
   1.710 +        return false;
   1.711 +
   1.712 +    str = js_toLowerCase(cx, str);
   1.713 +    if (!str)
   1.714 +        return false;
   1.715 +
   1.716 +    call.rval().setString(str);
   1.717 +    return true;
   1.718 +}
   1.719 +
   1.720 +static bool
   1.721 +str_toLowerCase(JSContext *cx, unsigned argc, Value *vp)
   1.722 +{
   1.723 +    return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp));
   1.724 +}
   1.725 +
   1.726 +static bool
   1.727 +str_toLocaleLowerCase(JSContext *cx, unsigned argc, Value *vp)
   1.728 +{
   1.729 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.730 +
   1.731 +    /*
   1.732 +     * Forcefully ignore the first (or any) argument and return toLowerCase(),
   1.733 +     * ECMA has reserved that argument, presumably for defining the locale.
   1.734 +     */
   1.735 +    if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) {
   1.736 +        RootedString str(cx, ThisToStringForStringProto(cx, args));
   1.737 +        if (!str)
   1.738 +            return false;
   1.739 +
   1.740 +        RootedValue result(cx);
   1.741 +        if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result))
   1.742 +            return false;
   1.743 +
   1.744 +        args.rval().set(result);
   1.745 +        return true;
   1.746 +    }
   1.747 +
   1.748 +    return ToLowerCaseHelper(cx, args);
   1.749 +}
   1.750 +
   1.751 +JSString* JS_FASTCALL
   1.752 +js_toUpperCase(JSContext *cx, JSString *str)
   1.753 +{
   1.754 +    size_t n = str->length();
   1.755 +    const jschar *s = str->getChars(cx);
   1.756 +    if (!s)
   1.757 +        return nullptr;
   1.758 +    jschar *news = cx->pod_malloc<jschar>(n + 1);
   1.759 +    if (!news)
   1.760 +        return nullptr;
   1.761 +    for (size_t i = 0; i < n; i++)
   1.762 +        news[i] = unicode::ToUpperCase(s[i]);
   1.763 +    news[n] = 0;
   1.764 +    str = js_NewString<CanGC>(cx, news, n);
   1.765 +    if (!str) {
   1.766 +        js_free(news);
   1.767 +        return nullptr;
   1.768 +    }
   1.769 +    return str;
   1.770 +}
   1.771 +
   1.772 +static bool
   1.773 +ToUpperCaseHelper(JSContext *cx, CallReceiver call)
   1.774 +{
   1.775 +    RootedString str(cx, ThisToStringForStringProto(cx, call));
   1.776 +    if (!str)
   1.777 +        return false;
   1.778 +
   1.779 +    str = js_toUpperCase(cx, str);
   1.780 +    if (!str)
   1.781 +        return false;
   1.782 +
   1.783 +    call.rval().setString(str);
   1.784 +    return true;
   1.785 +}
   1.786 +
   1.787 +static bool
   1.788 +str_toUpperCase(JSContext *cx, unsigned argc, Value *vp)
   1.789 +{
   1.790 +    return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp));
   1.791 +}
   1.792 +
   1.793 +static bool
   1.794 +str_toLocaleUpperCase(JSContext *cx, unsigned argc, Value *vp)
   1.795 +{
   1.796 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.797 +
   1.798 +    /*
   1.799 +     * Forcefully ignore the first (or any) argument and return toUpperCase(),
   1.800 +     * ECMA has reserved that argument, presumably for defining the locale.
   1.801 +     */
   1.802 +    if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) {
   1.803 +        RootedString str(cx, ThisToStringForStringProto(cx, args));
   1.804 +        if (!str)
   1.805 +            return false;
   1.806 +
   1.807 +        RootedValue result(cx);
   1.808 +        if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result))
   1.809 +            return false;
   1.810 +
   1.811 +        args.rval().set(result);
   1.812 +        return true;
   1.813 +    }
   1.814 +
   1.815 +    return ToUpperCaseHelper(cx, args);
   1.816 +}
   1.817 +
   1.818 +#if !EXPOSE_INTL_API
   1.819 +static bool
   1.820 +str_localeCompare(JSContext *cx, unsigned argc, Value *vp)
   1.821 +{
   1.822 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.823 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
   1.824 +    if (!str)
   1.825 +        return false;
   1.826 +
   1.827 +    RootedString thatStr(cx, ToString<CanGC>(cx, args.get(0)));
   1.828 +    if (!thatStr)
   1.829 +        return false;
   1.830 +
   1.831 +    if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeCompare) {
   1.832 +        RootedValue result(cx);
   1.833 +        if (!cx->runtime()->localeCallbacks->localeCompare(cx, str, thatStr, &result))
   1.834 +            return false;
   1.835 +
   1.836 +        args.rval().set(result);
   1.837 +        return true;
   1.838 +    }
   1.839 +
   1.840 +    int32_t result;
   1.841 +    if (!CompareStrings(cx, str, thatStr, &result))
   1.842 +        return false;
   1.843 +
   1.844 +    args.rval().setInt32(result);
   1.845 +    return true;
   1.846 +}
   1.847 +#endif
   1.848 +
   1.849 +#if EXPOSE_INTL_API
   1.850 +static const size_t SB_LENGTH = 32;
   1.851 +
   1.852 +/* ES6 20140210 draft 21.1.3.12. */
   1.853 +static bool
   1.854 +str_normalize(JSContext *cx, unsigned argc, Value *vp)
   1.855 +{
   1.856 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.857 +
   1.858 +    // Steps 1-3.
   1.859 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
   1.860 +    if (!str)
   1.861 +        return false;
   1.862 +
   1.863 +    // Step 4.
   1.864 +    UNormalizationMode form;
   1.865 +    if (!args.hasDefined(0)) {
   1.866 +        form = UNORM_NFC;
   1.867 +    } else {
   1.868 +        // Steps 5-6.
   1.869 +        Rooted<JSLinearString*> formStr(cx, ArgToRootedString(cx, args, 0));
   1.870 +        if (!formStr)
   1.871 +            return false;
   1.872 +
   1.873 +        // Step 7.
   1.874 +        if (formStr == cx->names().NFC) {
   1.875 +            form = UNORM_NFC;
   1.876 +        } else if (formStr == cx->names().NFD) {
   1.877 +            form = UNORM_NFD;
   1.878 +        } else if (formStr == cx->names().NFKC) {
   1.879 +            form = UNORM_NFKC;
   1.880 +        } else if (formStr == cx->names().NFKD) {
   1.881 +            form = UNORM_NFKD;
   1.882 +        } else {
   1.883 +            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
   1.884 +                                 JSMSG_INVALID_NORMALIZE_FORM);
   1.885 +            return false;
   1.886 +        }
   1.887 +    }
   1.888 +
   1.889 +    // Step 8.
   1.890 +    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
   1.891 +    if (!flatStr)
   1.892 +        return false;
   1.893 +    const UChar *srcChars = JSCharToUChar(flatStr->chars());
   1.894 +    int32_t srcLen = SafeCast<int32_t>(flatStr->length());
   1.895 +    StringBuffer chars(cx);
   1.896 +    if (!chars.resize(SB_LENGTH))
   1.897 +        return false;
   1.898 +    UErrorCode status = U_ZERO_ERROR;
   1.899 +    int32_t size = unorm_normalize(srcChars, srcLen, form, 0,
   1.900 +                                   JSCharToUChar(chars.begin()), SB_LENGTH,
   1.901 +                                   &status);
   1.902 +    if (status == U_BUFFER_OVERFLOW_ERROR) {
   1.903 +        if (!chars.resize(size))
   1.904 +            return false;
   1.905 +        status = U_ZERO_ERROR;
   1.906 +#ifdef DEBUG
   1.907 +        int32_t finalSize =
   1.908 +#endif
   1.909 +        unorm_normalize(srcChars, srcLen, form, 0,
   1.910 +                        JSCharToUChar(chars.begin()), size,
   1.911 +                        &status);
   1.912 +        MOZ_ASSERT(size == finalSize || U_FAILURE(status), "unorm_normalize behaved inconsistently");
   1.913 +    }
   1.914 +    if (U_FAILURE(status))
   1.915 +        return false;
   1.916 +    // Trim any unused characters.
   1.917 +    if (!chars.resize(size))
   1.918 +        return false;
   1.919 +    RootedString ns(cx, chars.finishString());
   1.920 +    if (!ns)
   1.921 +        return false;
   1.922 +
   1.923 +    // Step 9.
   1.924 +    args.rval().setString(ns);
   1.925 +    return true;
   1.926 +}
   1.927 +#endif
   1.928 +
   1.929 +bool
   1.930 +js_str_charAt(JSContext *cx, unsigned argc, Value *vp)
   1.931 +{
   1.932 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.933 +
   1.934 +    RootedString str(cx);
   1.935 +    size_t i;
   1.936 +    if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
   1.937 +        str = args.thisv().toString();
   1.938 +        i = size_t(args[0].toInt32());
   1.939 +        if (i >= str->length())
   1.940 +            goto out_of_range;
   1.941 +    } else {
   1.942 +        str = ThisToStringForStringProto(cx, args);
   1.943 +        if (!str)
   1.944 +            return false;
   1.945 +
   1.946 +        double d = 0.0;
   1.947 +        if (args.length() > 0 && !ToInteger(cx, args[0], &d))
   1.948 +            return false;
   1.949 +
   1.950 +        if (d < 0 || str->length() <= d)
   1.951 +            goto out_of_range;
   1.952 +        i = size_t(d);
   1.953 +    }
   1.954 +
   1.955 +    str = cx->staticStrings().getUnitStringForElement(cx, str, i);
   1.956 +    if (!str)
   1.957 +        return false;
   1.958 +    args.rval().setString(str);
   1.959 +    return true;
   1.960 +
   1.961 +  out_of_range:
   1.962 +    args.rval().setString(cx->runtime()->emptyString);
   1.963 +    return true;
   1.964 +}
   1.965 +
   1.966 +bool
   1.967 +js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp)
   1.968 +{
   1.969 +    CallArgs args = CallArgsFromVp(argc, vp);
   1.970 +
   1.971 +    RootedString str(cx);
   1.972 +    size_t i;
   1.973 +    if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) {
   1.974 +        str = args.thisv().toString();
   1.975 +        i = size_t(args[0].toInt32());
   1.976 +        if (i >= str->length())
   1.977 +            goto out_of_range;
   1.978 +    } else {
   1.979 +        str = ThisToStringForStringProto(cx, args);
   1.980 +        if (!str)
   1.981 +            return false;
   1.982 +
   1.983 +        double d = 0.0;
   1.984 +        if (args.length() > 0 && !ToInteger(cx, args[0], &d))
   1.985 +            return false;
   1.986 +
   1.987 +        if (d < 0 || str->length() <= d)
   1.988 +            goto out_of_range;
   1.989 +        i = size_t(d);
   1.990 +    }
   1.991 +
   1.992 +    jschar c;
   1.993 +    if (!str->getChar(cx, i, &c))
   1.994 +        return false;
   1.995 +    args.rval().setInt32(c);
   1.996 +    return true;
   1.997 +
   1.998 +out_of_range:
   1.999 +    args.rval().setNaN();
  1.1000 +    return true;
  1.1001 +}
  1.1002 +
  1.1003 +/*
  1.1004 + * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen.
  1.1005 + * The patlen argument must be positive and no greater than sBMHPatLenMax.
  1.1006 + *
  1.1007 + * Return the index of pat in text, or -1 if not found.
  1.1008 + */
  1.1009 +static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */
  1.1010 +static const uint32_t sBMHPatLenMax   = 255; /* skip table element is uint8_t */
  1.1011 +static const int      sBMHBadPattern  = -2;  /* return value if pat is not ISO-Latin-1 */
  1.1012 +
  1.1013 +int
  1.1014 +js_BoyerMooreHorspool(const jschar *text, uint32_t textlen,
  1.1015 +                      const jschar *pat, uint32_t patlen)
  1.1016 +{
  1.1017 +    uint8_t skip[sBMHCharSetSize];
  1.1018 +
  1.1019 +    JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax);
  1.1020 +    for (uint32_t i = 0; i < sBMHCharSetSize; i++)
  1.1021 +        skip[i] = (uint8_t)patlen;
  1.1022 +    uint32_t m = patlen - 1;
  1.1023 +    for (uint32_t i = 0; i < m; i++) {
  1.1024 +        jschar c = pat[i];
  1.1025 +        if (c >= sBMHCharSetSize)
  1.1026 +            return sBMHBadPattern;
  1.1027 +        skip[c] = (uint8_t)(m - i);
  1.1028 +    }
  1.1029 +    jschar c;
  1.1030 +    for (uint32_t k = m;
  1.1031 +         k < textlen;
  1.1032 +         k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) {
  1.1033 +        for (uint32_t i = k, j = m; ; i--, j--) {
  1.1034 +            if (text[i] != pat[j])
  1.1035 +                break;
  1.1036 +            if (j == 0)
  1.1037 +                return static_cast<int>(i);  /* safe: max string size */
  1.1038 +        }
  1.1039 +    }
  1.1040 +    return -1;
  1.1041 +}
  1.1042 +
  1.1043 +struct MemCmp {
  1.1044 +    typedef uint32_t Extent;
  1.1045 +    static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *, uint32_t patlen) {
  1.1046 +        return (patlen - 1) * sizeof(jschar);
  1.1047 +    }
  1.1048 +    static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
  1.1049 +        return memcmp(p, t, extent) == 0;
  1.1050 +    }
  1.1051 +};
  1.1052 +
  1.1053 +struct ManualCmp {
  1.1054 +    typedef const jschar *Extent;
  1.1055 +    static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) {
  1.1056 +        return pat + patlen;
  1.1057 +    }
  1.1058 +    static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) {
  1.1059 +        for (; p != extent; ++p, ++t) {
  1.1060 +            if (*p != *t)
  1.1061 +                return false;
  1.1062 +        }
  1.1063 +        return true;
  1.1064 +    }
  1.1065 +};
  1.1066 +
  1.1067 +template <class InnerMatch>
  1.1068 +static int
  1.1069 +UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen)
  1.1070 +{
  1.1071 +    JS_ASSERT(patlen > 0 && textlen > 0);
  1.1072 +    const jschar *textend = text + textlen - (patlen - 1);
  1.1073 +    const jschar p0 = *pat;
  1.1074 +    const jschar *const patNext = pat + 1;
  1.1075 +    const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
  1.1076 +    uint8_t fixup;
  1.1077 +
  1.1078 +    const jschar *t = text;
  1.1079 +    switch ((textend - t) & 7) {
  1.1080 +      case 0: if (*t++ == p0) { fixup = 8; goto match; }
  1.1081 +      case 7: if (*t++ == p0) { fixup = 7; goto match; }
  1.1082 +      case 6: if (*t++ == p0) { fixup = 6; goto match; }
  1.1083 +      case 5: if (*t++ == p0) { fixup = 5; goto match; }
  1.1084 +      case 4: if (*t++ == p0) { fixup = 4; goto match; }
  1.1085 +      case 3: if (*t++ == p0) { fixup = 3; goto match; }
  1.1086 +      case 2: if (*t++ == p0) { fixup = 2; goto match; }
  1.1087 +      case 1: if (*t++ == p0) { fixup = 1; goto match; }
  1.1088 +    }
  1.1089 +    while (t != textend) {
  1.1090 +      if (t[0] == p0) { t += 1; fixup = 8; goto match; }
  1.1091 +      if (t[1] == p0) { t += 2; fixup = 7; goto match; }
  1.1092 +      if (t[2] == p0) { t += 3; fixup = 6; goto match; }
  1.1093 +      if (t[3] == p0) { t += 4; fixup = 5; goto match; }
  1.1094 +      if (t[4] == p0) { t += 5; fixup = 4; goto match; }
  1.1095 +      if (t[5] == p0) { t += 6; fixup = 3; goto match; }
  1.1096 +      if (t[6] == p0) { t += 7; fixup = 2; goto match; }
  1.1097 +      if (t[7] == p0) { t += 8; fixup = 1; goto match; }
  1.1098 +        t += 8;
  1.1099 +        continue;
  1.1100 +        do {
  1.1101 +            if (*t++ == p0) {
  1.1102 +              match:
  1.1103 +                if (!InnerMatch::match(patNext, t, extent))
  1.1104 +                    goto failed_match;
  1.1105 +                return t - text - 1;
  1.1106 +            }
  1.1107 +          failed_match:;
  1.1108 +        } while (--fixup > 0);
  1.1109 +    }
  1.1110 +    return -1;
  1.1111 +}
  1.1112 +
  1.1113 +static MOZ_ALWAYS_INLINE int
  1.1114 +StringMatch(const jschar *text, uint32_t textlen,
  1.1115 +            const jschar *pat, uint32_t patlen)
  1.1116 +{
  1.1117 +    if (patlen == 0)
  1.1118 +        return 0;
  1.1119 +    if (textlen < patlen)
  1.1120 +        return -1;
  1.1121 +
  1.1122 +#if defined(__i386__) || defined(_M_IX86) || defined(__i386)
  1.1123 +    /*
  1.1124 +     * Given enough registers, the unrolled loop below is faster than the
  1.1125 +     * following loop. 32-bit x86 does not have enough registers.
  1.1126 +     */
  1.1127 +    if (patlen == 1) {
  1.1128 +        const jschar p0 = *pat;
  1.1129 +        for (const jschar *c = text, *end = text + textlen; c != end; ++c) {
  1.1130 +            if (*c == p0)
  1.1131 +                return c - text;
  1.1132 +        }
  1.1133 +        return -1;
  1.1134 +    }
  1.1135 +#endif
  1.1136 +
  1.1137 +    /*
  1.1138 +     * If the text or pattern string is short, BMH will be more expensive than
  1.1139 +     * the basic linear scan due to initialization cost and a more complex loop
  1.1140 +     * body. While the correct threshold is input-dependent, we can make a few
  1.1141 +     * conservative observations:
  1.1142 +     *  - When |textlen| is "big enough", the initialization time will be
  1.1143 +     *    proportionally small, so the worst-case slowdown is minimized.
  1.1144 +     *  - When |patlen| is "too small", even the best case for BMH will be
  1.1145 +     *    slower than a simple scan for large |textlen| due to the more complex
  1.1146 +     *    loop body of BMH.
  1.1147 +     * From this, the values for "big enough" and "too small" are determined
  1.1148 +     * empirically. See bug 526348.
  1.1149 +     */
  1.1150 +    if (textlen >= 512 && patlen >= 11 && patlen <= sBMHPatLenMax) {
  1.1151 +        int index = js_BoyerMooreHorspool(text, textlen, pat, patlen);
  1.1152 +        if (index != sBMHBadPattern)
  1.1153 +            return index;
  1.1154 +    }
  1.1155 +
  1.1156 +    /*
  1.1157 +     * For big patterns with large potential overlap we want the SIMD-optimized
  1.1158 +     * speed of memcmp. For small patterns, a simple loop is faster.
  1.1159 +     *
  1.1160 +     * FIXME: Linux memcmp performance is sad and the manual loop is faster.
  1.1161 +     */
  1.1162 +    return
  1.1163 +#if !defined(__linux__)
  1.1164 +           patlen > 128 ? UnrolledMatch<MemCmp>(text, textlen, pat, patlen)
  1.1165 +                        :
  1.1166 +#endif
  1.1167 +                          UnrolledMatch<ManualCmp>(text, textlen, pat, patlen);
  1.1168 +}
  1.1169 +
  1.1170 +static const size_t sRopeMatchThresholdRatioLog2 = 5;
  1.1171 +
  1.1172 +bool
  1.1173 +js::StringHasPattern(const jschar *text, uint32_t textlen,
  1.1174 +                     const jschar *pat, uint32_t patlen)
  1.1175 +{
  1.1176 +    return StringMatch(text, textlen, pat, patlen) != -1;
  1.1177 +}
  1.1178 +
  1.1179 +// When an algorithm does not need a string represented as a single linear
  1.1180 +// array of characters, this range utility may be used to traverse the string a
  1.1181 +// sequence of linear arrays of characters. This avoids flattening ropes.
  1.1182 +class StringSegmentRange
  1.1183 +{
  1.1184 +    // If malloc() shows up in any profiles from this vector, we can add a new
  1.1185 +    // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx.
  1.1186 +    AutoStringVector stack;
  1.1187 +    Rooted<JSLinearString*> cur;
  1.1188 +
  1.1189 +    bool settle(JSString *str) {
  1.1190 +        while (str->isRope()) {
  1.1191 +            JSRope &rope = str->asRope();
  1.1192 +            if (!stack.append(rope.rightChild()))
  1.1193 +                return false;
  1.1194 +            str = rope.leftChild();
  1.1195 +        }
  1.1196 +        cur = &str->asLinear();
  1.1197 +        return true;
  1.1198 +    }
  1.1199 +
  1.1200 +  public:
  1.1201 +    StringSegmentRange(JSContext *cx)
  1.1202 +      : stack(cx), cur(cx)
  1.1203 +    {}
  1.1204 +
  1.1205 +    MOZ_WARN_UNUSED_RESULT bool init(JSString *str) {
  1.1206 +        JS_ASSERT(stack.empty());
  1.1207 +        return settle(str);
  1.1208 +    }
  1.1209 +
  1.1210 +    bool empty() const {
  1.1211 +        return cur == nullptr;
  1.1212 +    }
  1.1213 +
  1.1214 +    JSLinearString *front() const {
  1.1215 +        JS_ASSERT(!cur->isRope());
  1.1216 +        return cur;
  1.1217 +    }
  1.1218 +
  1.1219 +    MOZ_WARN_UNUSED_RESULT bool popFront() {
  1.1220 +        JS_ASSERT(!empty());
  1.1221 +        if (stack.empty()) {
  1.1222 +            cur = nullptr;
  1.1223 +            return true;
  1.1224 +        }
  1.1225 +        return settle(stack.popCopy());
  1.1226 +    }
  1.1227 +};
  1.1228 +
  1.1229 +/*
  1.1230 + * RopeMatch takes the text to search and the pattern to search for in the text.
  1.1231 + * RopeMatch returns false on OOM and otherwise returns the match index through
  1.1232 + * the 'match' outparam (-1 for not found).
  1.1233 + */
  1.1234 +static bool
  1.1235 +RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match)
  1.1236 +{
  1.1237 +    JS_ASSERT(textstr->isRope());
  1.1238 +
  1.1239 +    if (patlen == 0) {
  1.1240 +        *match = 0;
  1.1241 +        return true;
  1.1242 +    }
  1.1243 +    if (textstr->length() < patlen) {
  1.1244 +        *match = -1;
  1.1245 +        return true;
  1.1246 +    }
  1.1247 +
  1.1248 +    /*
  1.1249 +     * List of leaf nodes in the rope. If we run out of memory when trying to
  1.1250 +     * append to this list, we can still fall back to StringMatch, so use the
  1.1251 +     * system allocator so we don't report OOM in that case.
  1.1252 +     */
  1.1253 +    Vector<JSLinearString *, 16, SystemAllocPolicy> strs;
  1.1254 +
  1.1255 +    /*
  1.1256 +     * We don't want to do rope matching if there is a poor node-to-char ratio,
  1.1257 +     * since this means spending a lot of time in the match loop below. We also
  1.1258 +     * need to build the list of leaf nodes. Do both here: iterate over the
  1.1259 +     * nodes so long as there are not too many.
  1.1260 +     */
  1.1261 +    {
  1.1262 +        size_t textstrlen = textstr->length();
  1.1263 +        size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2;
  1.1264 +        StringSegmentRange r(cx);
  1.1265 +        if (!r.init(textstr))
  1.1266 +            return false;
  1.1267 +        while (!r.empty()) {
  1.1268 +            if (threshold-- == 0 || !strs.append(r.front())) {
  1.1269 +                const jschar *chars = textstr->getChars(cx);
  1.1270 +                if (!chars)
  1.1271 +                    return false;
  1.1272 +                *match = StringMatch(chars, textstrlen, pat, patlen);
  1.1273 +                return true;
  1.1274 +            }
  1.1275 +            if (!r.popFront())
  1.1276 +                return false;
  1.1277 +        }
  1.1278 +    }
  1.1279 +
  1.1280 +    /* Absolute offset from the beginning of the logical string textstr. */
  1.1281 +    int pos = 0;
  1.1282 +
  1.1283 +    for (JSLinearString **outerp = strs.begin(); outerp != strs.end(); ++outerp) {
  1.1284 +        /* Try to find a match within 'outer'. */
  1.1285 +        JSLinearString *outer = *outerp;
  1.1286 +        const jschar *chars = outer->chars();
  1.1287 +        size_t len = outer->length();
  1.1288 +        int matchResult = StringMatch(chars, len, pat, patlen);
  1.1289 +        if (matchResult != -1) {
  1.1290 +            /* Matched! */
  1.1291 +            *match = pos + matchResult;
  1.1292 +            return true;
  1.1293 +        }
  1.1294 +
  1.1295 +        /* Try to find a match starting in 'outer' and running into other nodes. */
  1.1296 +        const jschar *const text = chars + (patlen > len ? 0 : len - patlen + 1);
  1.1297 +        const jschar *const textend = chars + len;
  1.1298 +        const jschar p0 = *pat;
  1.1299 +        const jschar *const p1 = pat + 1;
  1.1300 +        const jschar *const patend = pat + patlen;
  1.1301 +        for (const jschar *t = text; t != textend; ) {
  1.1302 +            if (*t++ != p0)
  1.1303 +                continue;
  1.1304 +            JSLinearString **innerp = outerp;
  1.1305 +            const jschar *ttend = textend;
  1.1306 +            for (const jschar *pp = p1, *tt = t; pp != patend; ++pp, ++tt) {
  1.1307 +                while (tt == ttend) {
  1.1308 +                    if (++innerp == strs.end()) {
  1.1309 +                        *match = -1;
  1.1310 +                        return true;
  1.1311 +                    }
  1.1312 +                    JSLinearString *inner = *innerp;
  1.1313 +                    tt = inner->chars();
  1.1314 +                    ttend = tt + inner->length();
  1.1315 +                }
  1.1316 +                if (*pp != *tt)
  1.1317 +                    goto break_continue;
  1.1318 +            }
  1.1319 +
  1.1320 +            /* Matched! */
  1.1321 +            *match = pos + (t - chars) - 1;  /* -1 because of *t++ above */
  1.1322 +            return true;
  1.1323 +
  1.1324 +          break_continue:;
  1.1325 +        }
  1.1326 +
  1.1327 +        pos += len;
  1.1328 +    }
  1.1329 +
  1.1330 +    *match = -1;
  1.1331 +    return true;
  1.1332 +}
  1.1333 +
  1.1334 +/* ES6 20121026 draft 15.5.4.24. */
  1.1335 +static bool
  1.1336 +str_contains(JSContext *cx, unsigned argc, Value *vp)
  1.1337 +{
  1.1338 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.1339 +
  1.1340 +    // Steps 1, 2, and 3
  1.1341 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.1342 +    if (!str)
  1.1343 +        return false;
  1.1344 +
  1.1345 +    // Steps 4 and 5
  1.1346 +    Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0));
  1.1347 +    if (!searchStr)
  1.1348 +        return false;
  1.1349 +
  1.1350 +    // Steps 6 and 7
  1.1351 +    uint32_t pos = 0;
  1.1352 +    if (args.hasDefined(1)) {
  1.1353 +        if (args[1].isInt32()) {
  1.1354 +            int i = args[1].toInt32();
  1.1355 +            pos = (i < 0) ? 0U : uint32_t(i);
  1.1356 +        } else {
  1.1357 +            double d;
  1.1358 +            if (!ToInteger(cx, args[1], &d))
  1.1359 +                return false;
  1.1360 +            pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
  1.1361 +        }
  1.1362 +    }
  1.1363 +
  1.1364 +    // Step 8
  1.1365 +    uint32_t textLen = str->length();
  1.1366 +    const jschar *textChars = str->getChars(cx);
  1.1367 +    if (!textChars)
  1.1368 +        return false;
  1.1369 +
  1.1370 +    // Step 9
  1.1371 +    uint32_t start = Min(Max(pos, 0U), textLen);
  1.1372 +
  1.1373 +    // Step 10
  1.1374 +    uint32_t searchLen = searchStr->length();
  1.1375 +    const jschar *searchChars = searchStr->chars();
  1.1376 +
  1.1377 +    // Step 11
  1.1378 +    textChars += start;
  1.1379 +    textLen -= start;
  1.1380 +    int match = StringMatch(textChars, textLen, searchChars, searchLen);
  1.1381 +    args.rval().setBoolean(match != -1);
  1.1382 +    return true;
  1.1383 +}
  1.1384 +
  1.1385 +/* ES6 20120927 draft 15.5.4.7. */
  1.1386 +static bool
  1.1387 +str_indexOf(JSContext *cx, unsigned argc, Value *vp)
  1.1388 +{
  1.1389 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.1390 +
  1.1391 +    // Steps 1, 2, and 3
  1.1392 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.1393 +    if (!str)
  1.1394 +        return false;
  1.1395 +
  1.1396 +    // Steps 4 and 5
  1.1397 +    Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0));
  1.1398 +    if (!searchStr)
  1.1399 +        return false;
  1.1400 +
  1.1401 +    // Steps 6 and 7
  1.1402 +    uint32_t pos = 0;
  1.1403 +    if (args.hasDefined(1)) {
  1.1404 +        if (args[1].isInt32()) {
  1.1405 +            int i = args[1].toInt32();
  1.1406 +            pos = (i < 0) ? 0U : uint32_t(i);
  1.1407 +        } else {
  1.1408 +            double d;
  1.1409 +            if (!ToInteger(cx, args[1], &d))
  1.1410 +                return false;
  1.1411 +            pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
  1.1412 +        }
  1.1413 +    }
  1.1414 +
  1.1415 +   // Step 8
  1.1416 +    uint32_t textLen = str->length();
  1.1417 +    const jschar *textChars = str->getChars(cx);
  1.1418 +    if (!textChars)
  1.1419 +        return false;
  1.1420 +
  1.1421 +    // Step 9
  1.1422 +    uint32_t start = Min(Max(pos, 0U), textLen);
  1.1423 +
  1.1424 +    // Step 10
  1.1425 +    uint32_t searchLen = searchStr->length();
  1.1426 +    const jschar *searchChars = searchStr->chars();
  1.1427 +
  1.1428 +    // Step 11
  1.1429 +    textChars += start;
  1.1430 +    textLen -= start;
  1.1431 +    int match = StringMatch(textChars, textLen, searchChars, searchLen);
  1.1432 +    args.rval().setInt32((match == -1) ? -1 : start + match);
  1.1433 +    return true;
  1.1434 +}
  1.1435 +
  1.1436 +static bool
  1.1437 +str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp)
  1.1438 +{
  1.1439 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.1440 +    RootedString textstr(cx, ThisToStringForStringProto(cx, args));
  1.1441 +    if (!textstr)
  1.1442 +        return false;
  1.1443 +
  1.1444 +    size_t textlen = textstr->length();
  1.1445 +
  1.1446 +    Rooted<JSLinearString*> patstr(cx, ArgToRootedString(cx, args, 0));
  1.1447 +    if (!patstr)
  1.1448 +        return false;
  1.1449 +
  1.1450 +    size_t patlen = patstr->length();
  1.1451 +
  1.1452 +    int i = textlen - patlen; // Start searching here
  1.1453 +    if (i < 0) {
  1.1454 +        args.rval().setInt32(-1);
  1.1455 +        return true;
  1.1456 +    }
  1.1457 +
  1.1458 +    if (args.length() > 1) {
  1.1459 +        if (args[1].isInt32()) {
  1.1460 +            int j = args[1].toInt32();
  1.1461 +            if (j <= 0)
  1.1462 +                i = 0;
  1.1463 +            else if (j < i)
  1.1464 +                i = j;
  1.1465 +        } else {
  1.1466 +            double d;
  1.1467 +            if (!ToNumber(cx, args[1], &d))
  1.1468 +                return false;
  1.1469 +            if (!IsNaN(d)) {
  1.1470 +                d = ToInteger(d);
  1.1471 +                if (d <= 0)
  1.1472 +                    i = 0;
  1.1473 +                else if (d < i)
  1.1474 +                    i = (int)d;
  1.1475 +            }
  1.1476 +        }
  1.1477 +    }
  1.1478 +
  1.1479 +    if (patlen == 0) {
  1.1480 +        args.rval().setInt32(i);
  1.1481 +        return true;
  1.1482 +    }
  1.1483 +
  1.1484 +    const jschar *text = textstr->getChars(cx);
  1.1485 +    if (!text)
  1.1486 +        return false;
  1.1487 +
  1.1488 +    const jschar *pat = patstr->chars();
  1.1489 +
  1.1490 +    const jschar *t = text + i;
  1.1491 +    const jschar *textend = text - 1;
  1.1492 +    const jschar p0 = *pat;
  1.1493 +    const jschar *patNext = pat + 1;
  1.1494 +    const jschar *patEnd = pat + patlen;
  1.1495 +
  1.1496 +    for (; t != textend; --t) {
  1.1497 +        if (*t == p0) {
  1.1498 +            const jschar *t1 = t + 1;
  1.1499 +            for (const jschar *p1 = patNext; p1 != patEnd; ++p1, ++t1) {
  1.1500 +                if (*t1 != *p1)
  1.1501 +                    goto break_continue;
  1.1502 +            }
  1.1503 +            args.rval().setInt32(t - text);
  1.1504 +            return true;
  1.1505 +        }
  1.1506 +      break_continue:;
  1.1507 +    }
  1.1508 +
  1.1509 +    args.rval().setInt32(-1);
  1.1510 +    return true;
  1.1511 +}
  1.1512 +
  1.1513 +/* ES6 20131108 draft 21.1.3.18. */
  1.1514 +static bool
  1.1515 +str_startsWith(JSContext *cx, unsigned argc, Value *vp)
  1.1516 +{
  1.1517 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.1518 +
  1.1519 +    // Steps 1, 2, and 3
  1.1520 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.1521 +    if (!str)
  1.1522 +        return false;
  1.1523 +
  1.1524 +    // Step 4
  1.1525 +    if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
  1.1526 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
  1.1527 +                             "first", "", "Regular Expression");
  1.1528 +        return false;
  1.1529 +    }
  1.1530 +
  1.1531 +    // Steps 5 and 6
  1.1532 +    Rooted<JSLinearString*> searchStr(cx, ArgToRootedString(cx, args, 0));
  1.1533 +    if (!searchStr)
  1.1534 +        return false;
  1.1535 +
  1.1536 +    // Steps 7 and 8
  1.1537 +    uint32_t pos = 0;
  1.1538 +    if (args.hasDefined(1)) {
  1.1539 +        if (args[1].isInt32()) {
  1.1540 +            int i = args[1].toInt32();
  1.1541 +            pos = (i < 0) ? 0U : uint32_t(i);
  1.1542 +        } else {
  1.1543 +            double d;
  1.1544 +            if (!ToInteger(cx, args[1], &d))
  1.1545 +                return false;
  1.1546 +            pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
  1.1547 +        }
  1.1548 +    }
  1.1549 +
  1.1550 +    // Step 9
  1.1551 +    uint32_t textLen = str->length();
  1.1552 +    const jschar *textChars = str->getChars(cx);
  1.1553 +    if (!textChars)
  1.1554 +        return false;
  1.1555 +
  1.1556 +    // Step 10
  1.1557 +    uint32_t start = Min(Max(pos, 0U), textLen);
  1.1558 +
  1.1559 +    // Step 11
  1.1560 +    uint32_t searchLen = searchStr->length();
  1.1561 +    const jschar *searchChars = searchStr->chars();
  1.1562 +
  1.1563 +    // Step 12
  1.1564 +    if (searchLen + start < searchLen || searchLen + start > textLen) {
  1.1565 +        args.rval().setBoolean(false);
  1.1566 +        return true;
  1.1567 +    }
  1.1568 +
  1.1569 +    // Steps 13 and 14
  1.1570 +    args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen));
  1.1571 +    return true;
  1.1572 +}
  1.1573 +
  1.1574 +/* ES6 20131108 draft 21.1.3.7. */
  1.1575 +static bool
  1.1576 +str_endsWith(JSContext *cx, unsigned argc, Value *vp)
  1.1577 +{
  1.1578 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.1579 +
  1.1580 +    // Steps 1, 2, and 3
  1.1581 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.1582 +    if (!str)
  1.1583 +        return false;
  1.1584 +
  1.1585 +    // Step 4
  1.1586 +    if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
  1.1587 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
  1.1588 +                             "first", "", "Regular Expression");
  1.1589 +        return false;
  1.1590 +    }
  1.1591 +
  1.1592 +    // Steps 5 and 6
  1.1593 +    Rooted<JSLinearString *> searchStr(cx, ArgToRootedString(cx, args, 0));
  1.1594 +    if (!searchStr)
  1.1595 +        return false;
  1.1596 +
  1.1597 +    // Step 7
  1.1598 +    uint32_t textLen = str->length();
  1.1599 +    const jschar *textChars = str->getChars(cx);
  1.1600 +    if (!textChars)
  1.1601 +        return false;
  1.1602 +
  1.1603 +    // Steps 8 and 9
  1.1604 +    uint32_t pos = textLen;
  1.1605 +    if (args.hasDefined(1)) {
  1.1606 +        if (args[1].isInt32()) {
  1.1607 +            int i = args[1].toInt32();
  1.1608 +            pos = (i < 0) ? 0U : uint32_t(i);
  1.1609 +        } else {
  1.1610 +            double d;
  1.1611 +            if (!ToInteger(cx, args[1], &d))
  1.1612 +                return false;
  1.1613 +            pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
  1.1614 +        }
  1.1615 +    }
  1.1616 +
  1.1617 +    // Step 10
  1.1618 +    uint32_t end = Min(Max(pos, 0U), textLen);
  1.1619 +
  1.1620 +    // Step 11
  1.1621 +    uint32_t searchLen = searchStr->length();
  1.1622 +    const jschar *searchChars = searchStr->chars();
  1.1623 +
  1.1624 +    // Step 13 (reordered)
  1.1625 +    if (searchLen > end) {
  1.1626 +        args.rval().setBoolean(false);
  1.1627 +        return true;
  1.1628 +    }
  1.1629 +
  1.1630 +    // Step 12
  1.1631 +    uint32_t start = end - searchLen;
  1.1632 +
  1.1633 +    // Steps 14 and 15
  1.1634 +    args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen));
  1.1635 +    return true;
  1.1636 +}
  1.1637 +
  1.1638 +static bool
  1.1639 +js_TrimString(JSContext *cx, Value *vp, bool trimLeft, bool trimRight)
  1.1640 +{
  1.1641 +    CallReceiver call = CallReceiverFromVp(vp);
  1.1642 +    RootedString str(cx, ThisToStringForStringProto(cx, call));
  1.1643 +    if (!str)
  1.1644 +        return false;
  1.1645 +    size_t length = str->length();
  1.1646 +    const jschar *chars = str->getChars(cx);
  1.1647 +    if (!chars)
  1.1648 +        return false;
  1.1649 +
  1.1650 +    size_t begin = 0;
  1.1651 +    size_t end = length;
  1.1652 +
  1.1653 +    if (trimLeft) {
  1.1654 +        while (begin < length && unicode::IsSpace(chars[begin]))
  1.1655 +            ++begin;
  1.1656 +    }
  1.1657 +
  1.1658 +    if (trimRight) {
  1.1659 +        while (end > begin && unicode::IsSpace(chars[end - 1]))
  1.1660 +            --end;
  1.1661 +    }
  1.1662 +
  1.1663 +    str = js_NewDependentString(cx, str, begin, end - begin);
  1.1664 +    if (!str)
  1.1665 +        return false;
  1.1666 +
  1.1667 +    call.rval().setString(str);
  1.1668 +    return true;
  1.1669 +}
  1.1670 +
  1.1671 +static bool
  1.1672 +str_trim(JSContext *cx, unsigned argc, Value *vp)
  1.1673 +{
  1.1674 +    return js_TrimString(cx, vp, true, true);
  1.1675 +}
  1.1676 +
  1.1677 +static bool
  1.1678 +str_trimLeft(JSContext *cx, unsigned argc, Value *vp)
  1.1679 +{
  1.1680 +    return js_TrimString(cx, vp, true, false);
  1.1681 +}
  1.1682 +
  1.1683 +static bool
  1.1684 +str_trimRight(JSContext *cx, unsigned argc, Value *vp)
  1.1685 +{
  1.1686 +    return js_TrimString(cx, vp, false, true);
  1.1687 +}
  1.1688 +
  1.1689 +/*
  1.1690 + * Perl-inspired string functions.
  1.1691 + */
  1.1692 +
  1.1693 +namespace {
  1.1694 +
  1.1695 +/* Result of a successfully performed flat match. */
  1.1696 +class FlatMatch
  1.1697 +{
  1.1698 +    RootedAtom patstr;
  1.1699 +    const jschar *pat;
  1.1700 +    size_t       patlen;
  1.1701 +    int32_t      match_;
  1.1702 +
  1.1703 +    friend class StringRegExpGuard;
  1.1704 +
  1.1705 +  public:
  1.1706 +    FlatMatch(JSContext *cx) : patstr(cx) {}
  1.1707 +    JSLinearString *pattern() const { return patstr; }
  1.1708 +    size_t patternLength() const { return patlen; }
  1.1709 +
  1.1710 +    /*
  1.1711 +     * Note: The match is -1 when the match is performed successfully,
  1.1712 +     * but no match is found.
  1.1713 +     */
  1.1714 +    int32_t match() const { return match_; }
  1.1715 +};
  1.1716 +
  1.1717 +} /* anonymous namespace */
  1.1718 +
  1.1719 +static inline bool
  1.1720 +IsRegExpMetaChar(jschar c)
  1.1721 +{
  1.1722 +    switch (c) {
  1.1723 +      /* Taken from the PatternCharacter production in 15.10.1. */
  1.1724 +      case '^': case '$': case '\\': case '.': case '*': case '+':
  1.1725 +      case '?': case '(': case ')': case '[': case ']': case '{':
  1.1726 +      case '}': case '|':
  1.1727 +        return true;
  1.1728 +      default:
  1.1729 +        return false;
  1.1730 +    }
  1.1731 +}
  1.1732 +
  1.1733 +static inline bool
  1.1734 +HasRegExpMetaChars(const jschar *chars, size_t length)
  1.1735 +{
  1.1736 +    for (size_t i = 0; i < length; ++i) {
  1.1737 +        if (IsRegExpMetaChar(chars[i]))
  1.1738 +            return true;
  1.1739 +    }
  1.1740 +    return false;
  1.1741 +}
  1.1742 +
  1.1743 +namespace {
  1.1744 +
  1.1745 +/*
  1.1746 + * StringRegExpGuard factors logic out of String regexp operations.
  1.1747 + *
  1.1748 + * |optarg| indicates in which argument position RegExp flags will be found, if
  1.1749 + * present. This is a Mozilla extension and not part of any ECMA spec.
  1.1750 + */
  1.1751 +class MOZ_STACK_CLASS StringRegExpGuard
  1.1752 +{
  1.1753 +    RegExpGuard re_;
  1.1754 +    FlatMatch   fm;
  1.1755 +    RootedObject obj_;
  1.1756 +
  1.1757 +    /*
  1.1758 +     * Upper bound on the number of characters we are willing to potentially
  1.1759 +     * waste on searching for RegExp meta-characters.
  1.1760 +     */
  1.1761 +    static const size_t MAX_FLAT_PAT_LEN = 256;
  1.1762 +
  1.1763 +    static JSAtom *
  1.1764 +    flattenPattern(JSContext *cx, JSAtom *patstr)
  1.1765 +    {
  1.1766 +        StringBuffer sb(cx);
  1.1767 +        if (!sb.reserve(patstr->length()))
  1.1768 +            return nullptr;
  1.1769 +
  1.1770 +        static const jschar ESCAPE_CHAR = '\\';
  1.1771 +        const jschar *chars = patstr->chars();
  1.1772 +        size_t len = patstr->length();
  1.1773 +        for (const jschar *it = chars; it != chars + len; ++it) {
  1.1774 +            if (IsRegExpMetaChar(*it)) {
  1.1775 +                if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
  1.1776 +                    return nullptr;
  1.1777 +            } else {
  1.1778 +                if (!sb.append(*it))
  1.1779 +                    return nullptr;
  1.1780 +            }
  1.1781 +        }
  1.1782 +        return sb.finishAtom();
  1.1783 +    }
  1.1784 +
  1.1785 +  public:
  1.1786 +    StringRegExpGuard(JSContext *cx)
  1.1787 +      : re_(cx), fm(cx), obj_(cx)
  1.1788 +    { }
  1.1789 +
  1.1790 +    /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
  1.1791 +    bool init(JSContext *cx, CallArgs args, bool convertVoid = false)
  1.1792 +    {
  1.1793 +        if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx))
  1.1794 +            return init(cx, &args[0].toObject());
  1.1795 +
  1.1796 +        if (convertVoid && !args.hasDefined(0)) {
  1.1797 +            fm.patstr = cx->runtime()->emptyString;
  1.1798 +            return true;
  1.1799 +        }
  1.1800 +
  1.1801 +        JSString *arg = ArgToRootedString(cx, args, 0);
  1.1802 +        if (!arg)
  1.1803 +            return false;
  1.1804 +
  1.1805 +        fm.patstr = AtomizeString(cx, arg);
  1.1806 +        if (!fm.patstr)
  1.1807 +            return false;
  1.1808 +
  1.1809 +        return true;
  1.1810 +    }
  1.1811 +
  1.1812 +    bool init(JSContext *cx, JSObject *regexp) {
  1.1813 +        obj_ = regexp;
  1.1814 +
  1.1815 +        JS_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx));
  1.1816 +
  1.1817 +        if (!RegExpToShared(cx, obj_, &re_))
  1.1818 +            return false;
  1.1819 +        return true;
  1.1820 +    }
  1.1821 +
  1.1822 +    bool init(JSContext *cx, HandleString pattern) {
  1.1823 +        fm.patstr = AtomizeString(cx, pattern);
  1.1824 +        if (!fm.patstr)
  1.1825 +            return false;
  1.1826 +        return true;
  1.1827 +    }
  1.1828 +
  1.1829 +    /*
  1.1830 +     * Attempt to match |patstr| to |textstr|. A flags argument, metachars in
  1.1831 +     * the pattern string, or a lengthy pattern string can thwart this process.
  1.1832 +     *
  1.1833 +     * |checkMetaChars| looks for regexp metachars in the pattern string.
  1.1834 +     *
  1.1835 +     * Return whether flat matching could be used.
  1.1836 +     *
  1.1837 +     * N.B. tryFlatMatch returns nullptr on OOM, so the caller must check
  1.1838 +     * cx->isExceptionPending().
  1.1839 +     */
  1.1840 +    const FlatMatch *
  1.1841 +    tryFlatMatch(JSContext *cx, JSString *textstr, unsigned optarg, unsigned argc,
  1.1842 +                 bool checkMetaChars = true)
  1.1843 +    {
  1.1844 +        if (re_.initialized())
  1.1845 +            return nullptr;
  1.1846 +
  1.1847 +        fm.pat = fm.patstr->chars();
  1.1848 +        fm.patlen = fm.patstr->length();
  1.1849 +
  1.1850 +        if (optarg < argc)
  1.1851 +            return nullptr;
  1.1852 +
  1.1853 +        if (checkMetaChars &&
  1.1854 +            (fm.patlen > MAX_FLAT_PAT_LEN || HasRegExpMetaChars(fm.pat, fm.patlen))) {
  1.1855 +            return nullptr;
  1.1856 +        }
  1.1857 +
  1.1858 +        /*
  1.1859 +         * textstr could be a rope, so we want to avoid flattening it for as
  1.1860 +         * long as possible.
  1.1861 +         */
  1.1862 +        if (textstr->isRope()) {
  1.1863 +            if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_))
  1.1864 +                return nullptr;
  1.1865 +        } else {
  1.1866 +            const jschar *text = textstr->asLinear().chars();
  1.1867 +            size_t textlen = textstr->length();
  1.1868 +            fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen);
  1.1869 +        }
  1.1870 +        return &fm;
  1.1871 +    }
  1.1872 +
  1.1873 +    /* If the pattern is not already a regular expression, make it so. */
  1.1874 +    bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args)
  1.1875 +    {
  1.1876 +        if (re_.initialized())
  1.1877 +            return true;
  1.1878 +
  1.1879 +        /* Build RegExp from pattern string. */
  1.1880 +        RootedString opt(cx);
  1.1881 +        if (optarg < args.length()) {
  1.1882 +            opt = ToString<CanGC>(cx, args[optarg]);
  1.1883 +            if (!opt)
  1.1884 +                return false;
  1.1885 +        } else {
  1.1886 +            opt = nullptr;
  1.1887 +        }
  1.1888 +
  1.1889 +        Rooted<JSAtom *> patstr(cx);
  1.1890 +        if (flat) {
  1.1891 +            patstr = flattenPattern(cx, fm.patstr);
  1.1892 +            if (!patstr)
  1.1893 +                return false;
  1.1894 +        } else {
  1.1895 +            patstr = fm.patstr;
  1.1896 +        }
  1.1897 +        JS_ASSERT(patstr);
  1.1898 +
  1.1899 +        return cx->compartment()->regExps.get(cx, patstr, opt, &re_);
  1.1900 +    }
  1.1901 +
  1.1902 +    bool zeroLastIndex(JSContext *cx) {
  1.1903 +        if (!regExpIsObject())
  1.1904 +            return true;
  1.1905 +
  1.1906 +        // Use a fast path for same-global RegExp objects with writable
  1.1907 +        // lastIndex.
  1.1908 +        if (obj_->is<RegExpObject>() && obj_->nativeLookup(cx, cx->names().lastIndex)->writable()) {
  1.1909 +            obj_->as<RegExpObject>().zeroLastIndex();
  1.1910 +            return true;
  1.1911 +        }
  1.1912 +
  1.1913 +        // Handle everything else generically (including throwing if .lastIndex is non-writable).
  1.1914 +        RootedValue zero(cx, Int32Value(0));
  1.1915 +        return JSObject::setProperty(cx, obj_, obj_, cx->names().lastIndex, &zero, true);
  1.1916 +    }
  1.1917 +
  1.1918 +    RegExpShared &regExp() { return *re_; }
  1.1919 +
  1.1920 +    bool regExpIsObject() { return obj_ != nullptr; }
  1.1921 +    HandleObject regExpObject() {
  1.1922 +        JS_ASSERT(regExpIsObject());
  1.1923 +        return obj_;
  1.1924 +    }
  1.1925 +
  1.1926 +  private:
  1.1927 +    StringRegExpGuard(const StringRegExpGuard &) MOZ_DELETE;
  1.1928 +    void operator=(const StringRegExpGuard &) MOZ_DELETE;
  1.1929 +};
  1.1930 +
  1.1931 +} /* anonymous namespace */
  1.1932 +
  1.1933 +static bool
  1.1934 +DoMatchLocal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input,
  1.1935 +             RegExpShared &re)
  1.1936 +{
  1.1937 +    size_t charsLen = input->length();
  1.1938 +    const jschar *chars = input->chars();
  1.1939 +
  1.1940 +    size_t i = 0;
  1.1941 +    ScopedMatchPairs matches(&cx->tempLifoAlloc());
  1.1942 +    RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches);
  1.1943 +    if (status == RegExpRunStatus_Error)
  1.1944 +        return false;
  1.1945 +
  1.1946 +    if (status == RegExpRunStatus_Success_NotFound) {
  1.1947 +        args.rval().setNull();
  1.1948 +        return true;
  1.1949 +    }
  1.1950 +
  1.1951 +    if (!res->updateFromMatchPairs(cx, input, matches))
  1.1952 +        return false;
  1.1953 +
  1.1954 +    RootedValue rval(cx);
  1.1955 +    if (!CreateRegExpMatchResult(cx, input, matches, &rval))
  1.1956 +        return false;
  1.1957 +
  1.1958 +    args.rval().set(rval);
  1.1959 +    return true;
  1.1960 +}
  1.1961 +
  1.1962 +/* ES5 15.5.4.10 step 8. */
  1.1963 +static bool
  1.1964 +DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input,
  1.1965 +              StringRegExpGuard &g)
  1.1966 +{
  1.1967 +    // Step 8a.
  1.1968 +    //
  1.1969 +    // This single zeroing of "lastIndex" covers all "lastIndex" changes in the
  1.1970 +    // rest of String.prototype.match, particularly in steps 8f(i) and
  1.1971 +    // 8f(iii)(2)(a).  Here's why.
  1.1972 +    //
  1.1973 +    // The inputs to the calls to RegExp.prototype.exec are a RegExp object
  1.1974 +    // whose .global is true and a string.  The only side effect of a call in
  1.1975 +    // these circumstances is that the RegExp's .lastIndex will be modified to
  1.1976 +    // the next starting index after the discovered match (or to 0 if there's
  1.1977 +    // no remaining match).  Because .lastIndex is a non-configurable data
  1.1978 +    // property and no script-controllable code executes after step 8a, passing
  1.1979 +    // step 8a implies *every* .lastIndex set succeeds.  String.prototype.match
  1.1980 +    // calls RegExp.prototype.exec repeatedly, and the last call doesn't match,
  1.1981 +    // so the final value of .lastIndex is 0: exactly the state after step 8a
  1.1982 +    // succeeds.  No spec step lets script observe intermediate .lastIndex
  1.1983 +    // values.
  1.1984 +    //
  1.1985 +    // The arrays returned by RegExp.prototype.exec always have a string at
  1.1986 +    // index 0, for which [[Get]]s have no side effects.
  1.1987 +    //
  1.1988 +    // Filling in a new array using [[DefineOwnProperty]] is unobservable.
  1.1989 +    //
  1.1990 +    // This is a tricky point, because after this set, our implementation *can*
  1.1991 +    // fail.  The key is that script can't distinguish these failure modes from
  1.1992 +    // one where, in spec terms, we fail immediately after step 8a.  That *in
  1.1993 +    // reality* we might have done extra matching work, or created a partial
  1.1994 +    // results array to return, or hit an interrupt, is irrelevant.  The
  1.1995 +    // script can't tell we did any of those things but didn't update
  1.1996 +    // .lastIndex.  Thus we can optimize steps 8b onward however we want,
  1.1997 +    // including eliminating intermediate .lastIndex sets, as long as we don't
  1.1998 +    // add ways for script to observe the intermediate states.
  1.1999 +    //
  1.2000 +    // In short: it's okay to cheat (by setting .lastIndex to 0, once) because
  1.2001 +    // we can't get caught.
  1.2002 +    if (!g.zeroLastIndex(cx))
  1.2003 +        return false;
  1.2004 +
  1.2005 +    // Step 8b.
  1.2006 +    AutoValueVector elements(cx);
  1.2007 +
  1.2008 +    size_t lastSuccessfulStart = 0;
  1.2009 +
  1.2010 +    // The loop variables from steps 8c-e aren't needed, as we use different
  1.2011 +    // techniques from the spec to implement step 8f's loop.
  1.2012 +
  1.2013 +    // Step 8f.
  1.2014 +    MatchPair match;
  1.2015 +    size_t charsLen = input->length();
  1.2016 +    const jschar *chars = input->chars();
  1.2017 +    RegExpShared &re = g.regExp();
  1.2018 +    for (size_t searchIndex = 0; searchIndex <= charsLen; ) {
  1.2019 +        if (!CheckForInterrupt(cx))
  1.2020 +            return false;
  1.2021 +
  1.2022 +        // Steps 8f(i-ii), minus "lastIndex" updates (see above).
  1.2023 +        size_t nextSearchIndex = searchIndex;
  1.2024 +        RegExpRunStatus status = re.executeMatchOnly(cx, chars, charsLen, &nextSearchIndex, match);
  1.2025 +        if (status == RegExpRunStatus_Error)
  1.2026 +            return false;
  1.2027 +
  1.2028 +        // Step 8f(ii).
  1.2029 +        if (status == RegExpRunStatus_Success_NotFound)
  1.2030 +            break;
  1.2031 +
  1.2032 +        lastSuccessfulStart = searchIndex;
  1.2033 +
  1.2034 +        // Steps 8f(iii)(1-3).
  1.2035 +        searchIndex = match.isEmpty() ? nextSearchIndex + 1 : nextSearchIndex;
  1.2036 +
  1.2037 +        // Step 8f(iii)(4-5).
  1.2038 +        JSLinearString *str = js_NewDependentString(cx, input, match.start, match.length());
  1.2039 +        if (!str)
  1.2040 +            return false;
  1.2041 +        if (!elements.append(StringValue(str)))
  1.2042 +            return false;
  1.2043 +    }
  1.2044 +
  1.2045 +    // Step 8g.
  1.2046 +    if (elements.empty()) {
  1.2047 +        args.rval().setNull();
  1.2048 +        return true;
  1.2049 +    }
  1.2050 +
  1.2051 +    // The last *successful* match updates the RegExpStatics. (Interestingly,
  1.2052 +    // this implies that String.prototype.match's semantics aren't those
  1.2053 +    // implied by the RegExp.prototype.exec calls in the ES5 algorithm.)
  1.2054 +    res->updateLazily(cx, input, &re, lastSuccessfulStart);
  1.2055 +
  1.2056 +    // Steps 8b, 8f(iii)(5-6), 8h.
  1.2057 +    JSObject *array = NewDenseCopiedArray(cx, elements.length(), elements.begin());
  1.2058 +    if (!array)
  1.2059 +        return false;
  1.2060 +
  1.2061 +    args.rval().setObject(*array);
  1.2062 +    return true;
  1.2063 +}
  1.2064 +
  1.2065 +static bool
  1.2066 +BuildFlatMatchArray(JSContext *cx, HandleString textstr, const FlatMatch &fm, CallArgs *args)
  1.2067 +{
  1.2068 +    if (fm.match() < 0) {
  1.2069 +        args->rval().setNull();
  1.2070 +        return true;
  1.2071 +    }
  1.2072 +
  1.2073 +    /* For this non-global match, produce a RegExp.exec-style array. */
  1.2074 +    RootedObject obj(cx, NewDenseEmptyArray(cx));
  1.2075 +    if (!obj)
  1.2076 +        return false;
  1.2077 +
  1.2078 +    RootedValue patternVal(cx, StringValue(fm.pattern()));
  1.2079 +    RootedValue matchVal(cx, Int32Value(fm.match()));
  1.2080 +    RootedValue textVal(cx, StringValue(textstr));
  1.2081 +
  1.2082 +    if (!JSObject::defineElement(cx, obj, 0, patternVal) ||
  1.2083 +        !JSObject::defineProperty(cx, obj, cx->names().index, matchVal) ||
  1.2084 +        !JSObject::defineProperty(cx, obj, cx->names().input, textVal))
  1.2085 +    {
  1.2086 +        return false;
  1.2087 +    }
  1.2088 +
  1.2089 +    args->rval().setObject(*obj);
  1.2090 +    return true;
  1.2091 +}
  1.2092 +
  1.2093 +/* ES5 15.5.4.10. */
  1.2094 +bool
  1.2095 +js::str_match(JSContext *cx, unsigned argc, Value *vp)
  1.2096 +{
  1.2097 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.2098 +
  1.2099 +    /* Steps 1-2. */
  1.2100 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.2101 +    if (!str)
  1.2102 +        return false;
  1.2103 +
  1.2104 +    /* Steps 3-4, plus the trailing-argument "flags" extension. */
  1.2105 +    StringRegExpGuard g(cx);
  1.2106 +    if (!g.init(cx, args, true))
  1.2107 +        return false;
  1.2108 +
  1.2109 +    /* Fast path when the search pattern can be searched for as a string. */
  1.2110 +    if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length()))
  1.2111 +        return BuildFlatMatchArray(cx, str, *fm, &args);
  1.2112 +
  1.2113 +    /* Return if there was an error in tryFlatMatch. */
  1.2114 +    if (cx->isExceptionPending())
  1.2115 +        return false;
  1.2116 +
  1.2117 +    /* Create regular-expression internals as needed to perform the match. */
  1.2118 +    if (!g.normalizeRegExp(cx, false, 1, args))
  1.2119 +        return false;
  1.2120 +
  1.2121 +    RegExpStatics *res = cx->global()->getRegExpStatics();
  1.2122 +    Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
  1.2123 +    if (!linearStr)
  1.2124 +        return false;
  1.2125 +
  1.2126 +    /* Steps 5-6, 7. */
  1.2127 +    if (!g.regExp().global())
  1.2128 +        return DoMatchLocal(cx, args, res, linearStr, g.regExp());
  1.2129 +
  1.2130 +    /* Steps 6, 8. */
  1.2131 +    return DoMatchGlobal(cx, args, res, linearStr, g);
  1.2132 +}
  1.2133 +
  1.2134 +bool
  1.2135 +js::str_search(JSContext *cx, unsigned argc, Value *vp)
  1.2136 +{
  1.2137 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.2138 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.2139 +    if (!str)
  1.2140 +        return false;
  1.2141 +
  1.2142 +    StringRegExpGuard g(cx);
  1.2143 +    if (!g.init(cx, args, true))
  1.2144 +        return false;
  1.2145 +    if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) {
  1.2146 +        args.rval().setInt32(fm->match());
  1.2147 +        return true;
  1.2148 +    }
  1.2149 +
  1.2150 +    if (cx->isExceptionPending())  /* from tryFlatMatch */
  1.2151 +        return false;
  1.2152 +
  1.2153 +    if (!g.normalizeRegExp(cx, false, 1, args))
  1.2154 +        return false;
  1.2155 +
  1.2156 +    Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
  1.2157 +    if (!linearStr)
  1.2158 +        return false;
  1.2159 +
  1.2160 +    const jschar *chars = linearStr->chars();
  1.2161 +    size_t length = linearStr->length();
  1.2162 +    RegExpStatics *res = cx->global()->getRegExpStatics();
  1.2163 +
  1.2164 +    /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
  1.2165 +    size_t i = 0;
  1.2166 +    MatchPair match;
  1.2167 +
  1.2168 +    RegExpRunStatus status = g.regExp().executeMatchOnly(cx, chars, length, &i, match);
  1.2169 +    if (status == RegExpRunStatus_Error)
  1.2170 +        return false;
  1.2171 +
  1.2172 +    if (status == RegExpRunStatus_Success)
  1.2173 +        res->updateLazily(cx, linearStr, &g.regExp(), 0);
  1.2174 +
  1.2175 +    JS_ASSERT_IF(status == RegExpRunStatus_Success_NotFound, match.start == -1);
  1.2176 +    args.rval().setInt32(match.start);
  1.2177 +    return true;
  1.2178 +}
  1.2179 +
  1.2180 +// Utility for building a rope (lazy concatenation) of strings.
  1.2181 +class RopeBuilder {
  1.2182 +    JSContext *cx;
  1.2183 +    RootedString res;
  1.2184 +
  1.2185 +    RopeBuilder(const RopeBuilder &other) MOZ_DELETE;
  1.2186 +    void operator=(const RopeBuilder &other) MOZ_DELETE;
  1.2187 +
  1.2188 +  public:
  1.2189 +    RopeBuilder(JSContext *cx)
  1.2190 +      : cx(cx), res(cx, cx->runtime()->emptyString)
  1.2191 +    {}
  1.2192 +
  1.2193 +    inline bool append(HandleString str) {
  1.2194 +        res = ConcatStrings<CanGC>(cx, res, str);
  1.2195 +        return !!res;
  1.2196 +    }
  1.2197 +
  1.2198 +    inline JSString *result() {
  1.2199 +        return res;
  1.2200 +    }
  1.2201 +};
  1.2202 +
  1.2203 +namespace {
  1.2204 +
  1.2205 +struct ReplaceData
  1.2206 +{
  1.2207 +    ReplaceData(JSContext *cx)
  1.2208 +      : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx),
  1.2209 +        fig(cx, NullValue()), sb(cx)
  1.2210 +    {}
  1.2211 +
  1.2212 +    inline void setReplacementString(JSLinearString *string) {
  1.2213 +        JS_ASSERT(string);
  1.2214 +        lambda = nullptr;
  1.2215 +        elembase = nullptr;
  1.2216 +        repstr = string;
  1.2217 +
  1.2218 +        /* We're about to store pointers into the middle of our string. */
  1.2219 +        dollarEnd = repstr->chars() + repstr->length();
  1.2220 +        dollar = js_strchr_limit(repstr->chars(), '$', dollarEnd);
  1.2221 +    }
  1.2222 +
  1.2223 +    inline void setReplacementFunction(JSObject *func) {
  1.2224 +        JS_ASSERT(func);
  1.2225 +        lambda = func;
  1.2226 +        elembase = nullptr;
  1.2227 +        repstr = nullptr;
  1.2228 +        dollar = dollarEnd = nullptr;
  1.2229 +    }
  1.2230 +
  1.2231 +    RootedString       str;            /* 'this' parameter object as a string */
  1.2232 +    StringRegExpGuard  g;              /* regexp parameter object and private data */
  1.2233 +    RootedObject       lambda;         /* replacement function object or null */
  1.2234 +    RootedObject       elembase;       /* object for function(a){return b[a]} replace */
  1.2235 +    Rooted<JSLinearString*> repstr; /* replacement string */
  1.2236 +    const jschar       *dollar;        /* null or pointer to first $ in repstr */
  1.2237 +    const jschar       *dollarEnd;     /* limit pointer for js_strchr_limit */
  1.2238 +    int                leftIndex;      /* left context index in str->chars */
  1.2239 +    JSSubString        dollarStr;      /* for "$$" InterpretDollar result */
  1.2240 +    bool               calledBack;     /* record whether callback has been called */
  1.2241 +    FastInvokeGuard    fig;            /* used for lambda calls, also holds arguments */
  1.2242 +    StringBuffer       sb;             /* buffer built during DoMatch */
  1.2243 +};
  1.2244 +
  1.2245 +} /* anonymous namespace */
  1.2246 +
  1.2247 +static bool
  1.2248 +ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata);
  1.2249 +
  1.2250 +static bool
  1.2251 +DoMatchForReplaceLocal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr,
  1.2252 +                       RegExpShared &re, ReplaceData &rdata)
  1.2253 +{
  1.2254 +    size_t charsLen = linearStr->length();
  1.2255 +    size_t i = 0;
  1.2256 +    ScopedMatchPairs matches(&cx->tempLifoAlloc());
  1.2257 +    RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches);
  1.2258 +    if (status == RegExpRunStatus_Error)
  1.2259 +        return false;
  1.2260 +
  1.2261 +    if (status == RegExpRunStatus_Success_NotFound)
  1.2262 +        return true;
  1.2263 +
  1.2264 +    if (!res->updateFromMatchPairs(cx, linearStr, matches))
  1.2265 +        return false;
  1.2266 +
  1.2267 +    return ReplaceRegExp(cx, res, rdata);
  1.2268 +}
  1.2269 +
  1.2270 +static bool
  1.2271 +DoMatchForReplaceGlobal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr,
  1.2272 +                        RegExpShared &re, ReplaceData &rdata)
  1.2273 +{
  1.2274 +    size_t charsLen = linearStr->length();
  1.2275 +    ScopedMatchPairs matches(&cx->tempLifoAlloc());
  1.2276 +    for (size_t count = 0, i = 0; i <= charsLen; ++count) {
  1.2277 +        if (!CheckForInterrupt(cx))
  1.2278 +            return false;
  1.2279 +
  1.2280 +        RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches);
  1.2281 +        if (status == RegExpRunStatus_Error)
  1.2282 +            return false;
  1.2283 +
  1.2284 +        if (status == RegExpRunStatus_Success_NotFound)
  1.2285 +            break;
  1.2286 +
  1.2287 +        if (!res->updateFromMatchPairs(cx, linearStr, matches))
  1.2288 +            return false;
  1.2289 +
  1.2290 +        if (!ReplaceRegExp(cx, res, rdata))
  1.2291 +            return false;
  1.2292 +        if (!res->matched())
  1.2293 +            ++i;
  1.2294 +    }
  1.2295 +
  1.2296 +    return true;
  1.2297 +}
  1.2298 +
  1.2299 +static bool
  1.2300 +InterpretDollar(RegExpStatics *res, const jschar *dp, const jschar *ep,
  1.2301 +                ReplaceData &rdata, JSSubString *out, size_t *skip)
  1.2302 +{
  1.2303 +    JS_ASSERT(*dp == '$');
  1.2304 +
  1.2305 +    /* If there is only a dollar, bail now */
  1.2306 +    if (dp + 1 >= ep)
  1.2307 +        return false;
  1.2308 +
  1.2309 +    /* Interpret all Perl match-induced dollar variables. */
  1.2310 +    jschar dc = dp[1];
  1.2311 +    if (JS7_ISDEC(dc)) {
  1.2312 +        /* ECMA-262 Edition 3: 1-9 or 01-99 */
  1.2313 +        unsigned num = JS7_UNDEC(dc);
  1.2314 +        if (num > res->getMatches().parenCount())
  1.2315 +            return false;
  1.2316 +
  1.2317 +        const jschar *cp = dp + 2;
  1.2318 +        if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
  1.2319 +            unsigned tmp = 10 * num + JS7_UNDEC(dc);
  1.2320 +            if (tmp <= res->getMatches().parenCount()) {
  1.2321 +                cp++;
  1.2322 +                num = tmp;
  1.2323 +            }
  1.2324 +        }
  1.2325 +        if (num == 0)
  1.2326 +            return false;
  1.2327 +
  1.2328 +        *skip = cp - dp;
  1.2329 +
  1.2330 +        JS_ASSERT(num <= res->getMatches().parenCount());
  1.2331 +
  1.2332 +        /*
  1.2333 +         * Note: we index to get the paren with the (1-indexed) pair
  1.2334 +         * number, as opposed to a (0-indexed) paren number.
  1.2335 +         */
  1.2336 +        res->getParen(num, out);
  1.2337 +        return true;
  1.2338 +    }
  1.2339 +
  1.2340 +    *skip = 2;
  1.2341 +    switch (dc) {
  1.2342 +      case '$':
  1.2343 +        rdata.dollarStr.chars = dp;
  1.2344 +        rdata.dollarStr.length = 1;
  1.2345 +        *out = rdata.dollarStr;
  1.2346 +        return true;
  1.2347 +      case '&':
  1.2348 +        res->getLastMatch(out);
  1.2349 +        return true;
  1.2350 +      case '+':
  1.2351 +        res->getLastParen(out);
  1.2352 +        return true;
  1.2353 +      case '`':
  1.2354 +        res->getLeftContext(out);
  1.2355 +        return true;
  1.2356 +      case '\'':
  1.2357 +        res->getRightContext(out);
  1.2358 +        return true;
  1.2359 +    }
  1.2360 +    return false;
  1.2361 +}
  1.2362 +
  1.2363 +static bool
  1.2364 +FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
  1.2365 +{
  1.2366 +    if (rdata.elembase) {
  1.2367 +        /*
  1.2368 +         * The base object is used when replace was passed a lambda which looks like
  1.2369 +         * 'function(a) { return b[a]; }' for the base object b.  b will not change
  1.2370 +         * in the course of the replace unless we end up making a scripted call due
  1.2371 +         * to accessing a scripted getter or a value with a scripted toString.
  1.2372 +         */
  1.2373 +        JS_ASSERT(rdata.lambda);
  1.2374 +        JS_ASSERT(!rdata.elembase->getOps()->lookupProperty);
  1.2375 +        JS_ASSERT(!rdata.elembase->getOps()->getProperty);
  1.2376 +
  1.2377 +        RootedValue match(cx);
  1.2378 +        if (!res->createLastMatch(cx, &match))
  1.2379 +            return false;
  1.2380 +        JSAtom *atom = ToAtom<CanGC>(cx, match);
  1.2381 +        if (!atom)
  1.2382 +            return false;
  1.2383 +
  1.2384 +        RootedValue v(cx);
  1.2385 +        if (HasDataProperty(cx, rdata.elembase, AtomToId(atom), v.address()) && v.isString()) {
  1.2386 +            rdata.repstr = v.toString()->ensureLinear(cx);
  1.2387 +            if (!rdata.repstr)
  1.2388 +                return false;
  1.2389 +            *sizep = rdata.repstr->length();
  1.2390 +            return true;
  1.2391 +        }
  1.2392 +
  1.2393 +        /*
  1.2394 +         * Couldn't handle this property, fall through and despecialize to the
  1.2395 +         * general lambda case.
  1.2396 +         */
  1.2397 +        rdata.elembase = nullptr;
  1.2398 +    }
  1.2399 +
  1.2400 +    if (rdata.lambda) {
  1.2401 +        RootedObject lambda(cx, rdata.lambda);
  1.2402 +        PreserveRegExpStatics staticsGuard(cx, res);
  1.2403 +        if (!staticsGuard.init(cx))
  1.2404 +            return false;
  1.2405 +
  1.2406 +        /*
  1.2407 +         * In the lambda case, not only do we find the replacement string's
  1.2408 +         * length, we compute repstr and return it via rdata for use within
  1.2409 +         * DoReplace.  The lambda is called with arguments ($&, $1, $2, ...,
  1.2410 +         * index, input), i.e., all the properties of a regexp match array.
  1.2411 +         * For $&, etc., we must create string jsvals from cx->regExpStatics.
  1.2412 +         * We grab up stack space to keep the newborn strings GC-rooted.
  1.2413 +         */
  1.2414 +        unsigned p = res->getMatches().parenCount();
  1.2415 +        unsigned argc = 1 + p + 2;
  1.2416 +
  1.2417 +        InvokeArgs &args = rdata.fig.args();
  1.2418 +        if (!args.init(argc))
  1.2419 +            return false;
  1.2420 +
  1.2421 +        args.setCallee(ObjectValue(*lambda));
  1.2422 +        args.setThis(UndefinedValue());
  1.2423 +
  1.2424 +        /* Push $&, $1, $2, ... */
  1.2425 +        unsigned argi = 0;
  1.2426 +        if (!res->createLastMatch(cx, args[argi++]))
  1.2427 +            return false;
  1.2428 +
  1.2429 +        for (size_t i = 0; i < res->getMatches().parenCount(); ++i) {
  1.2430 +            if (!res->createParen(cx, i + 1, args[argi++]))
  1.2431 +                return false;
  1.2432 +        }
  1.2433 +
  1.2434 +        /* Push match index and input string. */
  1.2435 +        args[argi++].setInt32(res->getMatches()[0].start);
  1.2436 +        args[argi].setString(rdata.str);
  1.2437 +
  1.2438 +        if (!rdata.fig.invoke(cx))
  1.2439 +            return false;
  1.2440 +
  1.2441 +        /* root repstr: rdata is on the stack, so scanned by conservative gc. */
  1.2442 +        JSString *repstr = ToString<CanGC>(cx, args.rval());
  1.2443 +        if (!repstr)
  1.2444 +            return false;
  1.2445 +        rdata.repstr = repstr->ensureLinear(cx);
  1.2446 +        if (!rdata.repstr)
  1.2447 +            return false;
  1.2448 +        *sizep = rdata.repstr->length();
  1.2449 +        return true;
  1.2450 +    }
  1.2451 +
  1.2452 +    JSString *repstr = rdata.repstr;
  1.2453 +    CheckedInt<uint32_t> replen = repstr->length();
  1.2454 +    for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp;
  1.2455 +         dp = js_strchr_limit(dp, '$', ep)) {
  1.2456 +        JSSubString sub;
  1.2457 +        size_t skip;
  1.2458 +        if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
  1.2459 +            if (sub.length > skip)
  1.2460 +                replen += sub.length - skip;
  1.2461 +            else
  1.2462 +                replen -= skip - sub.length;
  1.2463 +            dp += skip;
  1.2464 +        } else {
  1.2465 +            dp++;
  1.2466 +        }
  1.2467 +    }
  1.2468 +
  1.2469 +    if (!replen.isValid()) {
  1.2470 +        js_ReportAllocationOverflow(cx);
  1.2471 +        return false;
  1.2472 +    }
  1.2473 +
  1.2474 +    *sizep = replen.value();
  1.2475 +    return true;
  1.2476 +}
  1.2477 +
  1.2478 +/*
  1.2479 + * Precondition: |rdata.sb| already has necessary growth space reserved (as
  1.2480 + * derived from FindReplaceLength).
  1.2481 + */
  1.2482 +static void
  1.2483 +DoReplace(RegExpStatics *res, ReplaceData &rdata)
  1.2484 +{
  1.2485 +    JSLinearString *repstr = rdata.repstr;
  1.2486 +    const jschar *cp;
  1.2487 +    const jschar *bp = cp = repstr->chars();
  1.2488 +
  1.2489 +    const jschar *dp = rdata.dollar;
  1.2490 +    const jschar *ep = rdata.dollarEnd;
  1.2491 +    for (; dp; dp = js_strchr_limit(dp, '$', ep)) {
  1.2492 +        /* Move one of the constant portions of the replacement value. */
  1.2493 +        size_t len = dp - cp;
  1.2494 +        rdata.sb.infallibleAppend(cp, len);
  1.2495 +        cp = dp;
  1.2496 +
  1.2497 +        JSSubString sub;
  1.2498 +        size_t skip;
  1.2499 +        if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
  1.2500 +            len = sub.length;
  1.2501 +            rdata.sb.infallibleAppend(sub.chars, len);
  1.2502 +            cp += skip;
  1.2503 +            dp += skip;
  1.2504 +        } else {
  1.2505 +            dp++;
  1.2506 +        }
  1.2507 +    }
  1.2508 +    rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp));
  1.2509 +}
  1.2510 +
  1.2511 +static bool
  1.2512 +ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata)
  1.2513 +{
  1.2514 +
  1.2515 +    const MatchPair &match = res->getMatches()[0];
  1.2516 +    JS_ASSERT(!match.isUndefined());
  1.2517 +    JS_ASSERT(match.limit >= match.start && match.limit >= 0);
  1.2518 +
  1.2519 +    rdata.calledBack = true;
  1.2520 +    size_t leftoff = rdata.leftIndex;
  1.2521 +    size_t leftlen = match.start - leftoff;
  1.2522 +    rdata.leftIndex = match.limit;
  1.2523 +
  1.2524 +    size_t replen = 0;  /* silence 'unused' warning */
  1.2525 +    if (!FindReplaceLength(cx, res, rdata, &replen))
  1.2526 +        return false;
  1.2527 +
  1.2528 +    CheckedInt<uint32_t> newlen(rdata.sb.length());
  1.2529 +    newlen += leftlen;
  1.2530 +    newlen += replen;
  1.2531 +    if (!newlen.isValid()) {
  1.2532 +        js_ReportAllocationOverflow(cx);
  1.2533 +        return false;
  1.2534 +    }
  1.2535 +    if (!rdata.sb.reserve(newlen.value()))
  1.2536 +        return false;
  1.2537 +
  1.2538 +    JSLinearString &str = rdata.str->asLinear();  /* flattened for regexp */
  1.2539 +    const jschar *left = str.chars() + leftoff;
  1.2540 +
  1.2541 +    rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */
  1.2542 +    DoReplace(res, rdata);
  1.2543 +    return true;
  1.2544 +}
  1.2545 +
  1.2546 +static bool
  1.2547 +BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr,
  1.2548 +                     const FlatMatch &fm, MutableHandleValue rval)
  1.2549 +{
  1.2550 +    RopeBuilder builder(cx);
  1.2551 +    size_t match = fm.match();
  1.2552 +    size_t matchEnd = match + fm.patternLength();
  1.2553 +
  1.2554 +    if (textstr->isRope()) {
  1.2555 +        /*
  1.2556 +         * If we are replacing over a rope, avoid flattening it by iterating
  1.2557 +         * through it, building a new rope.
  1.2558 +         */
  1.2559 +        StringSegmentRange r(cx);
  1.2560 +        if (!r.init(textstr))
  1.2561 +            return false;
  1.2562 +        size_t pos = 0;
  1.2563 +        while (!r.empty()) {
  1.2564 +            RootedString str(cx, r.front());
  1.2565 +            size_t len = str->length();
  1.2566 +            size_t strEnd = pos + len;
  1.2567 +            if (pos < matchEnd && strEnd > match) {
  1.2568 +                /*
  1.2569 +                 * We need to special-case any part of the rope that overlaps
  1.2570 +                 * with the replacement string.
  1.2571 +                 */
  1.2572 +                if (match >= pos) {
  1.2573 +                    /*
  1.2574 +                     * If this part of the rope overlaps with the left side of
  1.2575 +                     * the pattern, then it must be the only one to overlap with
  1.2576 +                     * the first character in the pattern, so we include the
  1.2577 +                     * replacement string here.
  1.2578 +                     */
  1.2579 +                    RootedString leftSide(cx, js_NewDependentString(cx, str, 0, match - pos));
  1.2580 +                    if (!leftSide ||
  1.2581 +                        !builder.append(leftSide) ||
  1.2582 +                        !builder.append(repstr)) {
  1.2583 +                        return false;
  1.2584 +                    }
  1.2585 +                }
  1.2586 +
  1.2587 +                /*
  1.2588 +                 * If str runs off the end of the matched string, append the
  1.2589 +                 * last part of str.
  1.2590 +                 */
  1.2591 +                if (strEnd > matchEnd) {
  1.2592 +                    RootedString rightSide(cx, js_NewDependentString(cx, str, matchEnd - pos,
  1.2593 +                                                                     strEnd - matchEnd));
  1.2594 +                    if (!rightSide || !builder.append(rightSide))
  1.2595 +                        return false;
  1.2596 +                }
  1.2597 +            } else {
  1.2598 +                if (!builder.append(str))
  1.2599 +                    return false;
  1.2600 +            }
  1.2601 +            pos += str->length();
  1.2602 +            if (!r.popFront())
  1.2603 +                return false;
  1.2604 +        }
  1.2605 +    } else {
  1.2606 +        RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, match));
  1.2607 +        if (!leftSide)
  1.2608 +            return false;
  1.2609 +        RootedString rightSide(cx);
  1.2610 +        rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(),
  1.2611 +                                          textstr->length() - match - fm.patternLength());
  1.2612 +        if (!rightSide ||
  1.2613 +            !builder.append(leftSide) ||
  1.2614 +            !builder.append(repstr) ||
  1.2615 +            !builder.append(rightSide)) {
  1.2616 +            return false;
  1.2617 +        }
  1.2618 +    }
  1.2619 +
  1.2620 +    rval.setString(builder.result());
  1.2621 +    return true;
  1.2622 +}
  1.2623 +
  1.2624 +/*
  1.2625 + * Perform a linear-scan dollar substitution on the replacement text,
  1.2626 + * constructing a result string that looks like:
  1.2627 + *
  1.2628 + *      newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
  1.2629 + */
  1.2630 +static inline bool
  1.2631 +BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
  1.2632 +                       const jschar *firstDollar, const FlatMatch &fm, MutableHandleValue rval)
  1.2633 +{
  1.2634 +    Rooted<JSLinearString*> textstr(cx, textstrArg->ensureLinear(cx));
  1.2635 +    if (!textstr)
  1.2636 +        return false;
  1.2637 +
  1.2638 +    JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length());
  1.2639 +    size_t matchStart = fm.match();
  1.2640 +    size_t matchLimit = matchStart + fm.patternLength();
  1.2641 +
  1.2642 +    /*
  1.2643 +     * Most probably:
  1.2644 +     *
  1.2645 +     *      len(newstr) >= len(orig) - len(match) + len(replacement)
  1.2646 +     *
  1.2647 +     * Note that dollar vars _could_ make the resulting text smaller than this.
  1.2648 +     */
  1.2649 +    StringBuffer newReplaceChars(cx);
  1.2650 +    if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
  1.2651 +        return false;
  1.2652 +
  1.2653 +    /* Move the pre-dollar chunk in bulk. */
  1.2654 +    newReplaceChars.infallibleAppend(repstr->chars(), firstDollar);
  1.2655 +
  1.2656 +    /* Move the rest char-by-char, interpreting dollars as we encounter them. */
  1.2657 +#define ENSURE(__cond) if (!(__cond)) return false;
  1.2658 +    const jschar *repstrLimit = repstr->chars() + repstr->length();
  1.2659 +    for (const jschar *it = firstDollar; it < repstrLimit; ++it) {
  1.2660 +        if (*it != '$' || it == repstrLimit - 1) {
  1.2661 +            ENSURE(newReplaceChars.append(*it));
  1.2662 +            continue;
  1.2663 +        }
  1.2664 +
  1.2665 +        switch (*(it + 1)) {
  1.2666 +          case '$': /* Eat one of the dollars. */
  1.2667 +            ENSURE(newReplaceChars.append(*it));
  1.2668 +            break;
  1.2669 +          case '&':
  1.2670 +            ENSURE(newReplaceChars.append(textstr->chars() + matchStart,
  1.2671 +                                          textstr->chars() + matchLimit));
  1.2672 +            break;
  1.2673 +          case '`':
  1.2674 +            ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart));
  1.2675 +            break;
  1.2676 +          case '\'':
  1.2677 +            ENSURE(newReplaceChars.append(textstr->chars() + matchLimit,
  1.2678 +                                          textstr->chars() + textstr->length()));
  1.2679 +            break;
  1.2680 +          default: /* The dollar we saw was not special (no matter what its mother told it). */
  1.2681 +            ENSURE(newReplaceChars.append(*it));
  1.2682 +            continue;
  1.2683 +        }
  1.2684 +        ++it; /* We always eat an extra char in the above switch. */
  1.2685 +    }
  1.2686 +
  1.2687 +    RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, matchStart));
  1.2688 +    ENSURE(leftSide);
  1.2689 +
  1.2690 +    RootedString newReplace(cx, newReplaceChars.finishString());
  1.2691 +    ENSURE(newReplace);
  1.2692 +
  1.2693 +    JS_ASSERT(textstr->length() >= matchLimit);
  1.2694 +    RootedString rightSide(cx, js_NewDependentString(cx, textstr, matchLimit,
  1.2695 +                                                        textstr->length() - matchLimit));
  1.2696 +    ENSURE(rightSide);
  1.2697 +
  1.2698 +    RopeBuilder builder(cx);
  1.2699 +    ENSURE(builder.append(leftSide) &&
  1.2700 +           builder.append(newReplace) &&
  1.2701 +           builder.append(rightSide));
  1.2702 +#undef ENSURE
  1.2703 +
  1.2704 +    rval.setString(builder.result());
  1.2705 +    return true;
  1.2706 +}
  1.2707 +
  1.2708 +struct StringRange
  1.2709 +{
  1.2710 +    size_t start;
  1.2711 +    size_t length;
  1.2712 +
  1.2713 +    StringRange(size_t s, size_t l)
  1.2714 +      : start(s), length(l)
  1.2715 +    { }
  1.2716 +};
  1.2717 +
  1.2718 +static inline JSFatInlineString *
  1.2719 +FlattenSubstrings(JSContext *cx, const jschar *chars,
  1.2720 +                  const StringRange *ranges, size_t rangesLen, size_t outputLen)
  1.2721 +{
  1.2722 +    JS_ASSERT(JSFatInlineString::lengthFits(outputLen));
  1.2723 +
  1.2724 +    JSFatInlineString *str = js_NewGCFatInlineString<CanGC>(cx);
  1.2725 +    if (!str)
  1.2726 +        return nullptr;
  1.2727 +    jschar *buf = str->init(outputLen);
  1.2728 +
  1.2729 +    size_t pos = 0;
  1.2730 +    for (size_t i = 0; i < rangesLen; i++) {
  1.2731 +        PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length);
  1.2732 +        pos += ranges[i].length;
  1.2733 +    }
  1.2734 +    JS_ASSERT(pos == outputLen);
  1.2735 +
  1.2736 +    buf[outputLen] = 0;
  1.2737 +    return str;
  1.2738 +}
  1.2739 +
  1.2740 +static JSString *
  1.2741 +AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr,
  1.2742 +                 const StringRange *ranges, size_t rangesLen)
  1.2743 +{
  1.2744 +    JS_ASSERT(rangesLen);
  1.2745 +
  1.2746 +    /* For single substrings, construct a dependent string. */
  1.2747 +    if (rangesLen == 1)
  1.2748 +        return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length);
  1.2749 +
  1.2750 +    const jschar *chars = flatStr->getChars(cx);
  1.2751 +    if (!chars)
  1.2752 +        return nullptr;
  1.2753 +
  1.2754 +    /* Collect substrings into a rope */
  1.2755 +    size_t i = 0;
  1.2756 +    RopeBuilder rope(cx);
  1.2757 +    RootedString part(cx, nullptr);
  1.2758 +    while (i < rangesLen) {
  1.2759 +
  1.2760 +        /* Find maximum range that fits in JSFatInlineString */
  1.2761 +        size_t substrLen = 0;
  1.2762 +        size_t end = i;
  1.2763 +        for (; end < rangesLen; end++) {
  1.2764 +            if (substrLen + ranges[end].length > JSFatInlineString::MAX_FAT_INLINE_LENGTH)
  1.2765 +                break;
  1.2766 +            substrLen += ranges[end].length;
  1.2767 +        }
  1.2768 +
  1.2769 +        if (i == end) {
  1.2770 +            /* Not even one range fits JSFatInlineString, use DependentString */
  1.2771 +            const StringRange &sr = ranges[i++];
  1.2772 +            part = js_NewDependentString(cx, flatStr, sr.start, sr.length);
  1.2773 +        } else {
  1.2774 +            /* Copy the ranges (linearly) into a JSFatInlineString */
  1.2775 +            part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen);
  1.2776 +            i = end;
  1.2777 +        }
  1.2778 +
  1.2779 +        if (!part)
  1.2780 +            return nullptr;
  1.2781 +
  1.2782 +        /* Appending to the rope permanently roots the substring. */
  1.2783 +        if (!rope.append(part))
  1.2784 +            return nullptr;
  1.2785 +    }
  1.2786 +
  1.2787 +    return rope.result();
  1.2788 +}
  1.2789 +
  1.2790 +static bool
  1.2791 +StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, MutableHandleValue rval)
  1.2792 +{
  1.2793 +    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
  1.2794 +    if (!flatStr)
  1.2795 +        return false;
  1.2796 +
  1.2797 +    Vector<StringRange, 16, SystemAllocPolicy> ranges;
  1.2798 +
  1.2799 +    size_t charsLen = flatStr->length();
  1.2800 +
  1.2801 +    MatchPair match;
  1.2802 +    size_t startIndex = 0; /* Index used for iterating through the string. */
  1.2803 +    size_t lastIndex = 0;  /* Index after last successful match. */
  1.2804 +    size_t lazyIndex = 0;  /* Index before last successful match. */
  1.2805 +
  1.2806 +    /* Accumulate StringRanges for unmatched substrings. */
  1.2807 +    while (startIndex <= charsLen) {
  1.2808 +        if (!CheckForInterrupt(cx))
  1.2809 +            return false;
  1.2810 +
  1.2811 +        RegExpRunStatus status =
  1.2812 +            re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match);
  1.2813 +        if (status == RegExpRunStatus_Error)
  1.2814 +            return false;
  1.2815 +        if (status == RegExpRunStatus_Success_NotFound)
  1.2816 +            break;
  1.2817 +
  1.2818 +        /* Include the latest unmatched substring. */
  1.2819 +        if (size_t(match.start) > lastIndex) {
  1.2820 +            if (!ranges.append(StringRange(lastIndex, match.start - lastIndex)))
  1.2821 +                return false;
  1.2822 +        }
  1.2823 +
  1.2824 +        lazyIndex = lastIndex;
  1.2825 +        lastIndex = startIndex;
  1.2826 +
  1.2827 +        if (match.isEmpty())
  1.2828 +            startIndex++;
  1.2829 +
  1.2830 +        /* Non-global removal executes at most once. */
  1.2831 +        if (!re.global())
  1.2832 +            break;
  1.2833 +    }
  1.2834 +
  1.2835 +    /* If unmatched, return the input string. */
  1.2836 +    if (!lastIndex) {
  1.2837 +        if (startIndex > 0)
  1.2838 +            cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
  1.2839 +        rval.setString(str);
  1.2840 +        return true;
  1.2841 +    }
  1.2842 +
  1.2843 +    /* The last successful match updates the RegExpStatics. */
  1.2844 +    cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
  1.2845 +
  1.2846 +    /* Include any remaining part of the string. */
  1.2847 +    if (lastIndex < charsLen) {
  1.2848 +        if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex)))
  1.2849 +            return false;
  1.2850 +    }
  1.2851 +
  1.2852 +    /* Handle the empty string before calling .begin(). */
  1.2853 +    if (ranges.empty()) {
  1.2854 +        rval.setString(cx->runtime()->emptyString);
  1.2855 +        return true;
  1.2856 +    }
  1.2857 +
  1.2858 +    JSString *result = AppendSubstrings(cx, flatStr, ranges.begin(), ranges.length());
  1.2859 +    if (!result)
  1.2860 +        return false;
  1.2861 +
  1.2862 +    rval.setString(result);
  1.2863 +    return true;
  1.2864 +}
  1.2865 +
  1.2866 +static inline bool
  1.2867 +StrReplaceRegExp(JSContext *cx, ReplaceData &rdata, MutableHandleValue rval)
  1.2868 +{
  1.2869 +    rdata.leftIndex = 0;
  1.2870 +    rdata.calledBack = false;
  1.2871 +
  1.2872 +    RegExpStatics *res = cx->global()->getRegExpStatics();
  1.2873 +    RegExpShared &re = rdata.g.regExp();
  1.2874 +
  1.2875 +    // The spec doesn't describe this function very clearly, so we go ahead and
  1.2876 +    // assume that when the input to String.prototype.replace is a global
  1.2877 +    // RegExp, calling the replacer function (assuming one was provided) takes
  1.2878 +    // place only after the matching is done. See the comment at the beginning
  1.2879 +    // of DoMatchGlobal explaining why we can zero the the RegExp object's
  1.2880 +    // lastIndex property here.
  1.2881 +    if (re.global() && !rdata.g.zeroLastIndex(cx))
  1.2882 +        return false;
  1.2883 +
  1.2884 +    /* Optimize removal. */
  1.2885 +    if (rdata.repstr && rdata.repstr->length() == 0) {
  1.2886 +        JS_ASSERT(!rdata.lambda && !rdata.elembase && !rdata.dollar);
  1.2887 +        return StrReplaceRegexpRemove(cx, rdata.str, re, rval);
  1.2888 +    }
  1.2889 +
  1.2890 +    Rooted<JSLinearString*> linearStr(cx, rdata.str->ensureLinear(cx));
  1.2891 +    if (!linearStr)
  1.2892 +        return false;
  1.2893 +
  1.2894 +    if (re.global()) {
  1.2895 +        if (!DoMatchForReplaceGlobal(cx, res, linearStr, re, rdata))
  1.2896 +            return false;
  1.2897 +    } else {
  1.2898 +        if (!DoMatchForReplaceLocal(cx, res, linearStr, re, rdata))
  1.2899 +            return false;
  1.2900 +    }
  1.2901 +
  1.2902 +    if (!rdata.calledBack) {
  1.2903 +        /* Didn't match, so the string is unmodified. */
  1.2904 +        rval.setString(rdata.str);
  1.2905 +        return true;
  1.2906 +    }
  1.2907 +
  1.2908 +    JSSubString sub;
  1.2909 +    res->getRightContext(&sub);
  1.2910 +    if (!rdata.sb.append(sub.chars, sub.length))
  1.2911 +        return false;
  1.2912 +
  1.2913 +    JSString *retstr = rdata.sb.finishString();
  1.2914 +    if (!retstr)
  1.2915 +        return false;
  1.2916 +
  1.2917 +    rval.setString(retstr);
  1.2918 +    return true;
  1.2919 +}
  1.2920 +
  1.2921 +static inline bool
  1.2922 +str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
  1.2923 +{
  1.2924 +    if (!rdata.g.normalizeRegExp(cx, true, 2, args))
  1.2925 +        return false;
  1.2926 +
  1.2927 +    return StrReplaceRegExp(cx, rdata, args.rval());
  1.2928 +}
  1.2929 +
  1.2930 +bool
  1.2931 +js::str_replace_regexp_raw(JSContext *cx, HandleString string, HandleObject regexp,
  1.2932 +                       HandleString replacement, MutableHandleValue rval)
  1.2933 +{
  1.2934 +    /* Optimize removal, so we don't have to create ReplaceData */
  1.2935 +    if (replacement->length() == 0) {
  1.2936 +        StringRegExpGuard guard(cx);
  1.2937 +        if (!guard.init(cx, regexp))
  1.2938 +            return false;
  1.2939 +
  1.2940 +        RegExpShared &re = guard.regExp();
  1.2941 +        return StrReplaceRegexpRemove(cx, string, re, rval);
  1.2942 +    }
  1.2943 +
  1.2944 +    ReplaceData rdata(cx);
  1.2945 +    rdata.str = string;
  1.2946 +
  1.2947 +    JSLinearString *repl = replacement->ensureLinear(cx);
  1.2948 +    if (!repl)
  1.2949 +        return false;
  1.2950 +
  1.2951 +    rdata.setReplacementString(repl);
  1.2952 +
  1.2953 +    if (!rdata.g.init(cx, regexp))
  1.2954 +        return false;
  1.2955 +
  1.2956 +    return StrReplaceRegExp(cx, rdata, rval);
  1.2957 +}
  1.2958 +
  1.2959 +static inline bool
  1.2960 +StrReplaceString(JSContext *cx, ReplaceData &rdata, const FlatMatch &fm, MutableHandleValue rval)
  1.2961 +{
  1.2962 +    /*
  1.2963 +     * Note: we could optimize the text.length == pattern.length case if we wanted,
  1.2964 +     * even in the presence of dollar metachars.
  1.2965 +     */
  1.2966 +    if (rdata.dollar)
  1.2967 +        return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, fm, rval);
  1.2968 +    return BuildFlatReplacement(cx, rdata.str, rdata.repstr, fm, rval);
  1.2969 +}
  1.2970 +
  1.2971 +static const uint32_t ReplaceOptArg = 2;
  1.2972 +
  1.2973 +bool
  1.2974 +js::str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern,
  1.2975 +                          HandleString replacement, MutableHandleValue rval)
  1.2976 +{
  1.2977 +    ReplaceData rdata(cx);
  1.2978 +
  1.2979 +    rdata.str = string;
  1.2980 +    JSLinearString *repl = replacement->ensureLinear(cx);
  1.2981 +    if (!repl)
  1.2982 +        return false;
  1.2983 +    rdata.setReplacementString(repl);
  1.2984 +
  1.2985 +    if (!rdata.g.init(cx, pattern))
  1.2986 +        return false;
  1.2987 +    const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, ReplaceOptArg, false);
  1.2988 +
  1.2989 +    if (fm->match() < 0) {
  1.2990 +        rval.setString(string);
  1.2991 +        return true;
  1.2992 +    }
  1.2993 +
  1.2994 +    return StrReplaceString(cx, rdata, *fm, rval);
  1.2995 +}
  1.2996 +
  1.2997 +static inline bool
  1.2998 +str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm)
  1.2999 +{
  1.3000 +    RootedString matchStr(cx, js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength()));
  1.3001 +    if (!matchStr)
  1.3002 +        return false;
  1.3003 +
  1.3004 +    /* lambda(matchStr, matchStart, textstr) */
  1.3005 +    static const uint32_t lambdaArgc = 3;
  1.3006 +    if (!rdata.fig.args().init(lambdaArgc))
  1.3007 +        return false;
  1.3008 +
  1.3009 +    CallArgs &args = rdata.fig.args();
  1.3010 +    args.setCallee(ObjectValue(*rdata.lambda));
  1.3011 +    args.setThis(UndefinedValue());
  1.3012 +
  1.3013 +    Value *sp = args.array();
  1.3014 +    sp[0].setString(matchStr);
  1.3015 +    sp[1].setInt32(fm.match());
  1.3016 +    sp[2].setString(rdata.str);
  1.3017 +
  1.3018 +    if (!rdata.fig.invoke(cx))
  1.3019 +        return false;
  1.3020 +
  1.3021 +    RootedString repstr(cx, ToString<CanGC>(cx, args.rval()));
  1.3022 +    if (!repstr)
  1.3023 +        return false;
  1.3024 +
  1.3025 +    RootedString leftSide(cx, js_NewDependentString(cx, rdata.str, 0, fm.match()));
  1.3026 +    if (!leftSide)
  1.3027 +        return false;
  1.3028 +
  1.3029 +    size_t matchLimit = fm.match() + fm.patternLength();
  1.3030 +    RootedString rightSide(cx, js_NewDependentString(cx, rdata.str, matchLimit,
  1.3031 +                                                        rdata.str->length() - matchLimit));
  1.3032 +    if (!rightSide)
  1.3033 +        return false;
  1.3034 +
  1.3035 +    RopeBuilder builder(cx);
  1.3036 +    if (!(builder.append(leftSide) &&
  1.3037 +          builder.append(repstr) &&
  1.3038 +          builder.append(rightSide))) {
  1.3039 +        return false;
  1.3040 +    }
  1.3041 +
  1.3042 +    outerArgs.rval().setString(builder.result());
  1.3043 +    return true;
  1.3044 +}
  1.3045 +
  1.3046 +/*
  1.3047 + * Pattern match the script to check if it is is indexing into a particular
  1.3048 + * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
  1.3049 + * such cases, which are used by javascript packers (particularly the popular
  1.3050 + * Dean Edwards packer) to efficiently encode large scripts. We only handle the
  1.3051 + * code patterns generated by such packers here.
  1.3052 + */
  1.3053 +static bool
  1.3054 +LambdaIsGetElem(JSContext *cx, JSObject &lambda, MutableHandleObject pobj)
  1.3055 +{
  1.3056 +    if (!lambda.is<JSFunction>())
  1.3057 +        return true;
  1.3058 +
  1.3059 +    RootedFunction fun(cx, &lambda.as<JSFunction>());
  1.3060 +    if (!fun->isInterpreted())
  1.3061 +        return true;
  1.3062 +
  1.3063 +    JSScript *script = fun->getOrCreateScript(cx);
  1.3064 +    if (!script)
  1.3065 +        return false;
  1.3066 +
  1.3067 +    jsbytecode *pc = script->code();
  1.3068 +
  1.3069 +    /*
  1.3070 +     * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'.
  1.3071 +     * Rule out the (unlikely) possibility of a heavyweight function since it
  1.3072 +     * would make our scope walk off by 1.
  1.3073 +     */
  1.3074 +    if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->isHeavyweight())
  1.3075 +        return true;
  1.3076 +    ScopeCoordinate sc(pc);
  1.3077 +    ScopeObject *scope = &fun->environment()->as<ScopeObject>();
  1.3078 +    for (unsigned i = 0; i < sc.hops(); ++i)
  1.3079 +        scope = &scope->enclosingScope().as<ScopeObject>();
  1.3080 +    Value b = scope->aliasedVar(sc);
  1.3081 +    pc += JSOP_GETALIASEDVAR_LENGTH;
  1.3082 +
  1.3083 +    /* Look for 'a' to be the lambda's first argument. */
  1.3084 +    if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0)
  1.3085 +        return true;
  1.3086 +    pc += JSOP_GETARG_LENGTH;
  1.3087 +
  1.3088 +    /* 'b[a]' */
  1.3089 +    if (JSOp(*pc) != JSOP_GETELEM)
  1.3090 +        return true;
  1.3091 +    pc += JSOP_GETELEM_LENGTH;
  1.3092 +
  1.3093 +    /* 'return b[a]' */
  1.3094 +    if (JSOp(*pc) != JSOP_RETURN)
  1.3095 +        return true;
  1.3096 +
  1.3097 +    /* 'b' must behave like a normal object. */
  1.3098 +    if (!b.isObject())
  1.3099 +        return true;
  1.3100 +
  1.3101 +    JSObject &bobj = b.toObject();
  1.3102 +    const Class *clasp = bobj.getClass();
  1.3103 +    if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty)
  1.3104 +        return true;
  1.3105 +
  1.3106 +    pobj.set(&bobj);
  1.3107 +    return true;
  1.3108 +}
  1.3109 +
  1.3110 +bool
  1.3111 +js::str_replace(JSContext *cx, unsigned argc, Value *vp)
  1.3112 +{
  1.3113 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3114 +
  1.3115 +    ReplaceData rdata(cx);
  1.3116 +    rdata.str = ThisToStringForStringProto(cx, args);
  1.3117 +    if (!rdata.str)
  1.3118 +        return false;
  1.3119 +
  1.3120 +    if (!rdata.g.init(cx, args))
  1.3121 +        return false;
  1.3122 +
  1.3123 +    /* Extract replacement string/function. */
  1.3124 +    if (args.length() >= ReplaceOptArg && js_IsCallable(args[1])) {
  1.3125 +        rdata.setReplacementFunction(&args[1].toObject());
  1.3126 +
  1.3127 +        if (!LambdaIsGetElem(cx, *rdata.lambda, &rdata.elembase))
  1.3128 +            return false;
  1.3129 +    } else {
  1.3130 +        JSLinearString *string = ArgToRootedString(cx, args, 1);
  1.3131 +        if (!string)
  1.3132 +            return false;
  1.3133 +
  1.3134 +        rdata.setReplacementString(string);
  1.3135 +    }
  1.3136 +
  1.3137 +    rdata.fig.initFunction(ObjectOrNullValue(rdata.lambda));
  1.3138 +
  1.3139 +    /*
  1.3140 +     * Unlike its |String.prototype| brethren, |replace| doesn't convert
  1.3141 +     * its input to a regular expression. (Even if it contains metachars.)
  1.3142 +     *
  1.3143 +     * However, if the user invokes our (non-standard) |flags| argument
  1.3144 +     * extension then we revert to creating a regular expression. Note that
  1.3145 +     * this is observable behavior through the side-effect mutation of the
  1.3146 +     * |RegExp| statics.
  1.3147 +     */
  1.3148 +
  1.3149 +    const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false);
  1.3150 +
  1.3151 +    if (!fm) {
  1.3152 +        if (cx->isExceptionPending())  /* oom in RopeMatch in tryFlatMatch */
  1.3153 +            return false;
  1.3154 +        return str_replace_regexp(cx, args, rdata);
  1.3155 +    }
  1.3156 +
  1.3157 +    if (fm->match() < 0) {
  1.3158 +        args.rval().setString(rdata.str);
  1.3159 +        return true;
  1.3160 +    }
  1.3161 +
  1.3162 +    if (rdata.lambda)
  1.3163 +        return str_replace_flat_lambda(cx, args, rdata, *fm);
  1.3164 +    return StrReplaceString(cx, rdata, *fm, args.rval());
  1.3165 +}
  1.3166 +
  1.3167 +namespace {
  1.3168 +
  1.3169 +class SplitMatchResult {
  1.3170 +    size_t endIndex_;
  1.3171 +    size_t length_;
  1.3172 +
  1.3173 +  public:
  1.3174 +    void setFailure() {
  1.3175 +        JS_STATIC_ASSERT(SIZE_MAX > JSString::MAX_LENGTH);
  1.3176 +        endIndex_ = SIZE_MAX;
  1.3177 +    }
  1.3178 +    bool isFailure() const {
  1.3179 +        return endIndex_ == SIZE_MAX;
  1.3180 +    }
  1.3181 +    size_t endIndex() const {
  1.3182 +        JS_ASSERT(!isFailure());
  1.3183 +        return endIndex_;
  1.3184 +    }
  1.3185 +    size_t length() const {
  1.3186 +        JS_ASSERT(!isFailure());
  1.3187 +        return length_;
  1.3188 +    }
  1.3189 +    void setResult(size_t length, size_t endIndex) {
  1.3190 +        length_ = length;
  1.3191 +        endIndex_ = endIndex;
  1.3192 +    }
  1.3193 +};
  1.3194 +
  1.3195 +} /* anonymous namespace */
  1.3196 +
  1.3197 +template<class Matcher>
  1.3198 +static ArrayObject *
  1.3199 +SplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit, const Matcher &splitMatch,
  1.3200 +            Handle<TypeObject*> type)
  1.3201 +{
  1.3202 +    size_t strLength = str->length();
  1.3203 +    SplitMatchResult result;
  1.3204 +
  1.3205 +    /* Step 11. */
  1.3206 +    if (strLength == 0) {
  1.3207 +        if (!splitMatch(cx, str, 0, &result))
  1.3208 +            return nullptr;
  1.3209 +
  1.3210 +        /*
  1.3211 +         * NB: Unlike in the non-empty string case, it's perfectly fine
  1.3212 +         *     (indeed the spec requires it) if we match at the end of the
  1.3213 +         *     string.  Thus these cases should hold:
  1.3214 +         *
  1.3215 +         *   var a = "".split("");
  1.3216 +         *   assertEq(a.length, 0);
  1.3217 +         *   var b = "".split(/.?/);
  1.3218 +         *   assertEq(b.length, 0);
  1.3219 +         */
  1.3220 +        if (!result.isFailure())
  1.3221 +            return NewDenseEmptyArray(cx);
  1.3222 +
  1.3223 +        RootedValue v(cx, StringValue(str));
  1.3224 +        return NewDenseCopiedArray(cx, 1, v.address());
  1.3225 +    }
  1.3226 +
  1.3227 +    /* Step 12. */
  1.3228 +    size_t lastEndIndex = 0;
  1.3229 +    size_t index = 0;
  1.3230 +
  1.3231 +    /* Step 13. */
  1.3232 +    AutoValueVector splits(cx);
  1.3233 +
  1.3234 +    while (index < strLength) {
  1.3235 +        /* Step 13(a). */
  1.3236 +        if (!splitMatch(cx, str, index, &result))
  1.3237 +            return nullptr;
  1.3238 +
  1.3239 +        /*
  1.3240 +         * Step 13(b).
  1.3241 +         *
  1.3242 +         * Our match algorithm differs from the spec in that it returns the
  1.3243 +         * next index at which a match happens.  If no match happens we're
  1.3244 +         * done.
  1.3245 +         *
  1.3246 +         * But what if the match is at the end of the string (and the string is
  1.3247 +         * not empty)?  Per 13(c)(ii) this shouldn't be a match, so we have to
  1.3248 +         * specially exclude it.  Thus this case should hold:
  1.3249 +         *
  1.3250 +         *   var a = "abc".split(/\b/);
  1.3251 +         *   assertEq(a.length, 1);
  1.3252 +         *   assertEq(a[0], "abc");
  1.3253 +         */
  1.3254 +        if (result.isFailure())
  1.3255 +            break;
  1.3256 +
  1.3257 +        /* Step 13(c)(i). */
  1.3258 +        size_t sepLength = result.length();
  1.3259 +        size_t endIndex = result.endIndex();
  1.3260 +        if (sepLength == 0 && endIndex == strLength)
  1.3261 +            break;
  1.3262 +
  1.3263 +        /* Step 13(c)(ii). */
  1.3264 +        if (endIndex == lastEndIndex) {
  1.3265 +            index++;
  1.3266 +            continue;
  1.3267 +        }
  1.3268 +
  1.3269 +        /* Step 13(c)(iii). */
  1.3270 +        JS_ASSERT(lastEndIndex < endIndex);
  1.3271 +        JS_ASSERT(sepLength <= strLength);
  1.3272 +        JS_ASSERT(lastEndIndex + sepLength <= endIndex);
  1.3273 +
  1.3274 +        /* Steps 13(c)(iii)(1-3). */
  1.3275 +        size_t subLength = size_t(endIndex - sepLength - lastEndIndex);
  1.3276 +        JSString *sub = js_NewDependentString(cx, str, lastEndIndex, subLength);
  1.3277 +        if (!sub || !splits.append(StringValue(sub)))
  1.3278 +            return nullptr;
  1.3279 +
  1.3280 +        /* Step 13(c)(iii)(4). */
  1.3281 +        if (splits.length() == limit)
  1.3282 +            return NewDenseCopiedArray(cx, splits.length(), splits.begin());
  1.3283 +
  1.3284 +        /* Step 13(c)(iii)(5). */
  1.3285 +        lastEndIndex = endIndex;
  1.3286 +
  1.3287 +        /* Step 13(c)(iii)(6-7). */
  1.3288 +        if (Matcher::returnsCaptures) {
  1.3289 +            RegExpStatics *res = cx->global()->getRegExpStatics();
  1.3290 +            const MatchPairs &matches = res->getMatches();
  1.3291 +            for (size_t i = 0; i < matches.parenCount(); i++) {
  1.3292 +                /* Steps 13(c)(iii)(7)(a-c). */
  1.3293 +                if (!matches[i + 1].isUndefined()) {
  1.3294 +                    JSSubString parsub;
  1.3295 +                    res->getParen(i + 1, &parsub);
  1.3296 +                    sub = js_NewStringCopyN<CanGC>(cx, parsub.chars, parsub.length);
  1.3297 +                    if (!sub || !splits.append(StringValue(sub)))
  1.3298 +                        return nullptr;
  1.3299 +                } else {
  1.3300 +                    /* Only string entries have been accounted for so far. */
  1.3301 +                    AddTypePropertyId(cx, type, JSID_VOID, UndefinedValue());
  1.3302 +                    if (!splits.append(UndefinedValue()))
  1.3303 +                        return nullptr;
  1.3304 +                }
  1.3305 +
  1.3306 +                /* Step 13(c)(iii)(7)(d). */
  1.3307 +                if (splits.length() == limit)
  1.3308 +                    return NewDenseCopiedArray(cx, splits.length(), splits.begin());
  1.3309 +            }
  1.3310 +        }
  1.3311 +
  1.3312 +        /* Step 13(c)(iii)(8). */
  1.3313 +        index = lastEndIndex;
  1.3314 +    }
  1.3315 +
  1.3316 +    /* Steps 14-15. */
  1.3317 +    JSString *sub = js_NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex);
  1.3318 +    if (!sub || !splits.append(StringValue(sub)))
  1.3319 +        return nullptr;
  1.3320 +
  1.3321 +    /* Step 16. */
  1.3322 +    return NewDenseCopiedArray(cx, splits.length(), splits.begin());
  1.3323 +}
  1.3324 +
  1.3325 +// Fast-path for splitting a string into a character array via split("").
  1.3326 +static ArrayObject *
  1.3327 +CharSplitHelper(JSContext *cx, Handle<JSLinearString*> str, uint32_t limit)
  1.3328 +{
  1.3329 +    size_t strLength = str->length();
  1.3330 +    if (strLength == 0)
  1.3331 +        return NewDenseEmptyArray(cx);
  1.3332 +
  1.3333 +    js::StaticStrings &staticStrings = cx->staticStrings();
  1.3334 +    uint32_t resultlen = (limit < strLength ? limit : strLength);
  1.3335 +
  1.3336 +    AutoValueVector splits(cx);
  1.3337 +    if (!splits.reserve(resultlen))
  1.3338 +        return nullptr;
  1.3339 +
  1.3340 +    for (size_t i = 0; i < resultlen; ++i) {
  1.3341 +        JSString *sub = staticStrings.getUnitStringForElement(cx, str, i);
  1.3342 +        if (!sub)
  1.3343 +            return nullptr;
  1.3344 +        splits.infallibleAppend(StringValue(sub));
  1.3345 +    }
  1.3346 +
  1.3347 +    return NewDenseCopiedArray(cx, splits.length(), splits.begin());
  1.3348 +}
  1.3349 +
  1.3350 +namespace {
  1.3351 +
  1.3352 +/*
  1.3353 + * The SplitMatch operation from ES5 15.5.4.14 is implemented using different
  1.3354 + * paths for regular expression and string separators.
  1.3355 + *
  1.3356 + * The algorithm differs from the spec in that the we return the next index at
  1.3357 + * which a match happens.
  1.3358 + */
  1.3359 +class SplitRegExpMatcher
  1.3360 +{
  1.3361 +    RegExpShared &re;
  1.3362 +    RegExpStatics *res;
  1.3363 +
  1.3364 +  public:
  1.3365 +    SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
  1.3366 +
  1.3367 +    static const bool returnsCaptures = true;
  1.3368 +
  1.3369 +    bool operator()(JSContext *cx, Handle<JSLinearString*> str, size_t index,
  1.3370 +                    SplitMatchResult *result) const
  1.3371 +    {
  1.3372 +        const jschar *chars = str->chars();
  1.3373 +        size_t length = str->length();
  1.3374 +
  1.3375 +        ScopedMatchPairs matches(&cx->tempLifoAlloc());
  1.3376 +        RegExpRunStatus status = re.execute(cx, chars, length, &index, matches);
  1.3377 +        if (status == RegExpRunStatus_Error)
  1.3378 +            return false;
  1.3379 +
  1.3380 +        if (status == RegExpRunStatus_Success_NotFound) {
  1.3381 +            result->setFailure();
  1.3382 +            return true;
  1.3383 +        }
  1.3384 +
  1.3385 +        if (!res->updateFromMatchPairs(cx, str, matches))
  1.3386 +            return false;
  1.3387 +
  1.3388 +        JSSubString sep;
  1.3389 +        res->getLastMatch(&sep);
  1.3390 +
  1.3391 +        result->setResult(sep.length, index);
  1.3392 +        return true;
  1.3393 +    }
  1.3394 +};
  1.3395 +
  1.3396 +class SplitStringMatcher
  1.3397 +{
  1.3398 +    Rooted<JSLinearString*> sep;
  1.3399 +
  1.3400 +  public:
  1.3401 +    SplitStringMatcher(JSContext *cx, HandleLinearString sep)
  1.3402 +      : sep(cx, sep)
  1.3403 +    {}
  1.3404 +
  1.3405 +    static const bool returnsCaptures = false;
  1.3406 +
  1.3407 +    bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res) const
  1.3408 +    {
  1.3409 +        JS_ASSERT(index == 0 || index < str->length());
  1.3410 +        const jschar *chars = str->chars();
  1.3411 +        int match = StringMatch(chars + index, str->length() - index,
  1.3412 +                                sep->chars(), sep->length());
  1.3413 +        if (match == -1)
  1.3414 +            res->setFailure();
  1.3415 +        else
  1.3416 +            res->setResult(sep->length(), index + match + sep->length());
  1.3417 +        return true;
  1.3418 +    }
  1.3419 +};
  1.3420 +
  1.3421 +} /* anonymous namespace */
  1.3422 +
  1.3423 +/* ES5 15.5.4.14 */
  1.3424 +bool
  1.3425 +js::str_split(JSContext *cx, unsigned argc, Value *vp)
  1.3426 +{
  1.3427 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3428 +
  1.3429 +    /* Steps 1-2. */
  1.3430 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.3431 +    if (!str)
  1.3432 +        return false;
  1.3433 +
  1.3434 +    RootedTypeObject type(cx, GetTypeCallerInitObject(cx, JSProto_Array));
  1.3435 +    if (!type)
  1.3436 +        return false;
  1.3437 +    AddTypePropertyId(cx, type, JSID_VOID, Type::StringType());
  1.3438 +
  1.3439 +    /* Step 5: Use the second argument as the split limit, if given. */
  1.3440 +    uint32_t limit;
  1.3441 +    if (args.hasDefined(1)) {
  1.3442 +        double d;
  1.3443 +        if (!ToNumber(cx, args[1], &d))
  1.3444 +            return false;
  1.3445 +        limit = ToUint32(d);
  1.3446 +    } else {
  1.3447 +        limit = UINT32_MAX;
  1.3448 +    }
  1.3449 +
  1.3450 +    /* Step 8. */
  1.3451 +    RegExpGuard re(cx);
  1.3452 +    RootedLinearString sepstr(cx);
  1.3453 +    bool sepDefined = args.hasDefined(0);
  1.3454 +    if (sepDefined) {
  1.3455 +        if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
  1.3456 +            RootedObject obj(cx, &args[0].toObject());
  1.3457 +            if (!RegExpToShared(cx, obj, &re))
  1.3458 +                return false;
  1.3459 +        } else {
  1.3460 +            sepstr = ArgToRootedString(cx, args, 0);
  1.3461 +            if (!sepstr)
  1.3462 +                return false;
  1.3463 +        }
  1.3464 +    }
  1.3465 +
  1.3466 +    /* Step 9. */
  1.3467 +    if (limit == 0) {
  1.3468 +        JSObject *aobj = NewDenseEmptyArray(cx);
  1.3469 +        if (!aobj)
  1.3470 +            return false;
  1.3471 +        aobj->setType(type);
  1.3472 +        args.rval().setObject(*aobj);
  1.3473 +        return true;
  1.3474 +    }
  1.3475 +
  1.3476 +    /* Step 10. */
  1.3477 +    if (!sepDefined) {
  1.3478 +        RootedValue v(cx, StringValue(str));
  1.3479 +        JSObject *aobj = NewDenseCopiedArray(cx, 1, v.address());
  1.3480 +        if (!aobj)
  1.3481 +            return false;
  1.3482 +        aobj->setType(type);
  1.3483 +        args.rval().setObject(*aobj);
  1.3484 +        return true;
  1.3485 +    }
  1.3486 +    Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
  1.3487 +    if (!linearStr)
  1.3488 +        return false;
  1.3489 +
  1.3490 +    /* Steps 11-15. */
  1.3491 +    RootedObject aobj(cx);
  1.3492 +    if (!re.initialized()) {
  1.3493 +        if (sepstr->length() == 0) {
  1.3494 +            aobj = CharSplitHelper(cx, linearStr, limit);
  1.3495 +        } else {
  1.3496 +            SplitStringMatcher matcher(cx, sepstr);
  1.3497 +            aobj = SplitHelper(cx, linearStr, limit, matcher, type);
  1.3498 +        }
  1.3499 +    } else {
  1.3500 +        SplitRegExpMatcher matcher(*re, cx->global()->getRegExpStatics());
  1.3501 +        aobj = SplitHelper(cx, linearStr, limit, matcher, type);
  1.3502 +    }
  1.3503 +    if (!aobj)
  1.3504 +        return false;
  1.3505 +
  1.3506 +    /* Step 16. */
  1.3507 +    aobj->setType(type);
  1.3508 +    args.rval().setObject(*aobj);
  1.3509 +    return true;
  1.3510 +}
  1.3511 +
  1.3512 +JSObject *
  1.3513 +js::str_split_string(JSContext *cx, HandleTypeObject type, HandleString str, HandleString sep)
  1.3514 +{
  1.3515 +    Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
  1.3516 +    if (!linearStr)
  1.3517 +        return nullptr;
  1.3518 +
  1.3519 +    Rooted<JSLinearString*> linearSep(cx, sep->ensureLinear(cx));
  1.3520 +    if (!linearSep)
  1.3521 +        return nullptr;
  1.3522 +
  1.3523 +    uint32_t limit = UINT32_MAX;
  1.3524 +
  1.3525 +    RootedObject aobj(cx);
  1.3526 +    if (linearSep->length() == 0) {
  1.3527 +        aobj = CharSplitHelper(cx, linearStr, limit);
  1.3528 +    } else {
  1.3529 +        SplitStringMatcher matcher(cx, linearSep);
  1.3530 +        aobj = SplitHelper(cx, linearStr, limit, matcher, type);
  1.3531 +    }
  1.3532 +
  1.3533 +    if (!aobj)
  1.3534 +        return nullptr;
  1.3535 +
  1.3536 +    aobj->setType(type);
  1.3537 +    return aobj;
  1.3538 +}
  1.3539 +
  1.3540 +static bool
  1.3541 +str_substr(JSContext *cx, unsigned argc, Value *vp)
  1.3542 +{
  1.3543 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3544 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.3545 +    if (!str)
  1.3546 +        return false;
  1.3547 +
  1.3548 +    int32_t length, len, begin;
  1.3549 +    if (args.length() > 0) {
  1.3550 +        length = int32_t(str->length());
  1.3551 +        if (!ValueToIntegerRange(cx, args[0], &begin))
  1.3552 +            return false;
  1.3553 +
  1.3554 +        if (begin >= length) {
  1.3555 +            args.rval().setString(cx->runtime()->emptyString);
  1.3556 +            return true;
  1.3557 +        }
  1.3558 +        if (begin < 0) {
  1.3559 +            begin += length; /* length + INT_MIN will always be less than 0 */
  1.3560 +            if (begin < 0)
  1.3561 +                begin = 0;
  1.3562 +        }
  1.3563 +
  1.3564 +        if (args.hasDefined(1)) {
  1.3565 +            if (!ValueToIntegerRange(cx, args[1], &len))
  1.3566 +                return false;
  1.3567 +
  1.3568 +            if (len <= 0) {
  1.3569 +                args.rval().setString(cx->runtime()->emptyString);
  1.3570 +                return true;
  1.3571 +            }
  1.3572 +
  1.3573 +            if (uint32_t(length) < uint32_t(begin + len))
  1.3574 +                len = length - begin;
  1.3575 +        } else {
  1.3576 +            len = length - begin;
  1.3577 +        }
  1.3578 +
  1.3579 +        str = DoSubstr(cx, str, size_t(begin), size_t(len));
  1.3580 +        if (!str)
  1.3581 +            return false;
  1.3582 +    }
  1.3583 +
  1.3584 +    args.rval().setString(str);
  1.3585 +    return true;
  1.3586 +}
  1.3587 +
  1.3588 +/*
  1.3589 + * Python-esque sequence operations.
  1.3590 + */
  1.3591 +static bool
  1.3592 +str_concat(JSContext *cx, unsigned argc, Value *vp)
  1.3593 +{
  1.3594 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3595 +    JSString *str = ThisToStringForStringProto(cx, args);
  1.3596 +    if (!str)
  1.3597 +        return false;
  1.3598 +
  1.3599 +    for (unsigned i = 0; i < args.length(); i++) {
  1.3600 +        JSString *argStr = ToString<NoGC>(cx, args[i]);
  1.3601 +        if (!argStr) {
  1.3602 +            RootedString strRoot(cx, str);
  1.3603 +            argStr = ToString<CanGC>(cx, args[i]);
  1.3604 +            if (!argStr)
  1.3605 +                return false;
  1.3606 +            str = strRoot;
  1.3607 +        }
  1.3608 +
  1.3609 +        JSString *next = ConcatStrings<NoGC>(cx, str, argStr);
  1.3610 +        if (next) {
  1.3611 +            str = next;
  1.3612 +        } else {
  1.3613 +            RootedString strRoot(cx, str), argStrRoot(cx, argStr);
  1.3614 +            str = ConcatStrings<CanGC>(cx, strRoot, argStrRoot);
  1.3615 +            if (!str)
  1.3616 +                return false;
  1.3617 +        }
  1.3618 +    }
  1.3619 +
  1.3620 +    args.rval().setString(str);
  1.3621 +    return true;
  1.3622 +}
  1.3623 +
  1.3624 +static bool
  1.3625 +str_slice(JSContext *cx, unsigned argc, Value *vp)
  1.3626 +{
  1.3627 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3628 +
  1.3629 +    if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) {
  1.3630 +        JSString *str = args.thisv().toString();
  1.3631 +        size_t begin = args[0].toInt32();
  1.3632 +        size_t end = str->length();
  1.3633 +        if (begin <= end) {
  1.3634 +            size_t length = end - begin;
  1.3635 +            if (length == 0) {
  1.3636 +                str = cx->runtime()->emptyString;
  1.3637 +            } else {
  1.3638 +                str = (length == 1)
  1.3639 +                      ? cx->staticStrings().getUnitStringForElement(cx, str, begin)
  1.3640 +                      : js_NewDependentString(cx, str, begin, length);
  1.3641 +                if (!str)
  1.3642 +                    return false;
  1.3643 +            }
  1.3644 +            args.rval().setString(str);
  1.3645 +            return true;
  1.3646 +        }
  1.3647 +    }
  1.3648 +
  1.3649 +    RootedString str(cx, ThisToStringForStringProto(cx, args));
  1.3650 +    if (!str)
  1.3651 +        return false;
  1.3652 +
  1.3653 +    if (args.length() != 0) {
  1.3654 +        double begin, end, length;
  1.3655 +
  1.3656 +        if (!ToInteger(cx, args[0], &begin))
  1.3657 +            return false;
  1.3658 +        length = str->length();
  1.3659 +        if (begin < 0) {
  1.3660 +            begin += length;
  1.3661 +            if (begin < 0)
  1.3662 +                begin = 0;
  1.3663 +        } else if (begin > length) {
  1.3664 +            begin = length;
  1.3665 +        }
  1.3666 +
  1.3667 +        if (args.hasDefined(1)) {
  1.3668 +            if (!ToInteger(cx, args[1], &end))
  1.3669 +                return false;
  1.3670 +            if (end < 0) {
  1.3671 +                end += length;
  1.3672 +                if (end < 0)
  1.3673 +                    end = 0;
  1.3674 +            } else if (end > length) {
  1.3675 +                end = length;
  1.3676 +            }
  1.3677 +            if (end < begin)
  1.3678 +                end = begin;
  1.3679 +        } else {
  1.3680 +            end = length;
  1.3681 +        }
  1.3682 +
  1.3683 +        str = js_NewDependentString(cx, str,
  1.3684 +                                    (size_t)begin,
  1.3685 +                                    (size_t)(end - begin));
  1.3686 +        if (!str)
  1.3687 +            return false;
  1.3688 +    }
  1.3689 +    args.rval().setString(str);
  1.3690 +    return true;
  1.3691 +}
  1.3692 +
  1.3693 +#if JS_HAS_STR_HTML_HELPERS
  1.3694 +/*
  1.3695 + * HTML composition aids.
  1.3696 + */
  1.3697 +static bool
  1.3698 +tagify(JSContext *cx, const char *begin, HandleLinearString param, const char *end,
  1.3699 +       CallReceiver call)
  1.3700 +{
  1.3701 +    JSString *thisstr = ThisToStringForStringProto(cx, call);
  1.3702 +    if (!thisstr)
  1.3703 +        return false;
  1.3704 +
  1.3705 +    JSLinearString *str = thisstr->ensureLinear(cx);
  1.3706 +    if (!str)
  1.3707 +        return false;
  1.3708 +
  1.3709 +    if (!end)
  1.3710 +        end = begin;
  1.3711 +
  1.3712 +    size_t beglen = strlen(begin);
  1.3713 +    size_t taglen = 1 + beglen + 1;                     /* '<begin' + '>' */
  1.3714 +    if (param) {
  1.3715 +        size_t numChars = param->length();
  1.3716 +        const jschar *parchars = param->chars();
  1.3717 +        for (size_t i = 0, parlen = numChars; i < parlen; ++i) {
  1.3718 +            if (parchars[i] == '"')
  1.3719 +                numChars += 5;                          /* len(&quot;) - len(") */
  1.3720 +        }
  1.3721 +        taglen += 2 + numChars + 1;                     /* '="param"' */
  1.3722 +    }
  1.3723 +    size_t endlen = strlen(end);
  1.3724 +    taglen += str->length() + 2 + endlen + 1;           /* 'str</end>' */
  1.3725 +
  1.3726 +
  1.3727 +    StringBuffer sb(cx);
  1.3728 +    if (!sb.reserve(taglen))
  1.3729 +        return false;
  1.3730 +
  1.3731 +    sb.infallibleAppend('<');
  1.3732 +
  1.3733 +    MOZ_ALWAYS_TRUE(sb.appendInflated(begin, beglen));
  1.3734 +
  1.3735 +    if (param) {
  1.3736 +        sb.infallibleAppend('=');
  1.3737 +        sb.infallibleAppend('"');
  1.3738 +        const jschar *parchars = param->chars();
  1.3739 +        for (size_t i = 0, parlen = param->length(); i < parlen; ++i) {
  1.3740 +            if (parchars[i] != '"') {
  1.3741 +                sb.infallibleAppend(parchars[i]);
  1.3742 +            } else {
  1.3743 +                MOZ_ALWAYS_TRUE(sb.append("&quot;"));
  1.3744 +            }
  1.3745 +        }
  1.3746 +        sb.infallibleAppend('"');
  1.3747 +    }
  1.3748 +
  1.3749 +    sb.infallibleAppend('>');
  1.3750 +
  1.3751 +    MOZ_ALWAYS_TRUE(sb.append(str));
  1.3752 +
  1.3753 +    sb.infallibleAppend('<');
  1.3754 +    sb.infallibleAppend('/');
  1.3755 +
  1.3756 +    MOZ_ALWAYS_TRUE(sb.appendInflated(end, endlen));
  1.3757 +
  1.3758 +    sb.infallibleAppend('>');
  1.3759 +
  1.3760 +    JSFlatString *retstr = sb.finishString();
  1.3761 +    if (!retstr)
  1.3762 +        return false;
  1.3763 +
  1.3764 +    call.rval().setString(retstr);
  1.3765 +    return true;
  1.3766 +}
  1.3767 +
  1.3768 +static bool
  1.3769 +tagify_value(JSContext *cx, CallArgs args, const char *begin, const char *end)
  1.3770 +{
  1.3771 +    RootedLinearString param(cx, ArgToRootedString(cx, args, 0));
  1.3772 +    if (!param)
  1.3773 +        return false;
  1.3774 +
  1.3775 +    return tagify(cx, begin, param, end, args);
  1.3776 +}
  1.3777 +
  1.3778 +static bool
  1.3779 +str_bold(JSContext *cx, unsigned argc, Value *vp)
  1.3780 +{
  1.3781 +    return tagify(cx, "b", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3782 +}
  1.3783 +
  1.3784 +static bool
  1.3785 +str_italics(JSContext *cx, unsigned argc, Value *vp)
  1.3786 +{
  1.3787 +    return tagify(cx, "i", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3788 +}
  1.3789 +
  1.3790 +static bool
  1.3791 +str_fixed(JSContext *cx, unsigned argc, Value *vp)
  1.3792 +{
  1.3793 +    return tagify(cx, "tt", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3794 +}
  1.3795 +
  1.3796 +static bool
  1.3797 +str_fontsize(JSContext *cx, unsigned argc, Value *vp)
  1.3798 +{
  1.3799 +    return tagify_value(cx, CallArgsFromVp(argc, vp), "font size", "font");
  1.3800 +}
  1.3801 +
  1.3802 +static bool
  1.3803 +str_fontcolor(JSContext *cx, unsigned argc, Value *vp)
  1.3804 +{
  1.3805 +    return tagify_value(cx, CallArgsFromVp(argc, vp), "font color", "font");
  1.3806 +}
  1.3807 +
  1.3808 +static bool
  1.3809 +str_link(JSContext *cx, unsigned argc, Value *vp)
  1.3810 +{
  1.3811 +    return tagify_value(cx, CallArgsFromVp(argc, vp), "a href", "a");
  1.3812 +}
  1.3813 +
  1.3814 +static bool
  1.3815 +str_anchor(JSContext *cx, unsigned argc, Value *vp)
  1.3816 +{
  1.3817 +    return tagify_value(cx, CallArgsFromVp(argc, vp), "a name", "a");
  1.3818 +}
  1.3819 +
  1.3820 +static bool
  1.3821 +str_strike(JSContext *cx, unsigned argc, Value *vp)
  1.3822 +{
  1.3823 +    return tagify(cx, "strike", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3824 +}
  1.3825 +
  1.3826 +static bool
  1.3827 +str_small(JSContext *cx, unsigned argc, Value *vp)
  1.3828 +{
  1.3829 +    return tagify(cx, "small", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3830 +}
  1.3831 +
  1.3832 +static bool
  1.3833 +str_big(JSContext *cx, unsigned argc, Value *vp)
  1.3834 +{
  1.3835 +    return tagify(cx, "big", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3836 +}
  1.3837 +
  1.3838 +static bool
  1.3839 +str_blink(JSContext *cx, unsigned argc, Value *vp)
  1.3840 +{
  1.3841 +    return tagify(cx, "blink", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3842 +}
  1.3843 +
  1.3844 +static bool
  1.3845 +str_sup(JSContext *cx, unsigned argc, Value *vp)
  1.3846 +{
  1.3847 +    return tagify(cx, "sup", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3848 +}
  1.3849 +
  1.3850 +static bool
  1.3851 +str_sub(JSContext *cx, unsigned argc, Value *vp)
  1.3852 +{
  1.3853 +    return tagify(cx, "sub", NullPtr(), nullptr, CallReceiverFromVp(vp));
  1.3854 +}
  1.3855 +#endif /* JS_HAS_STR_HTML_HELPERS */
  1.3856 +
  1.3857 +static const JSFunctionSpec string_methods[] = {
  1.3858 +#if JS_HAS_TOSOURCE
  1.3859 +    JS_FN("quote",             str_quote,             0,JSFUN_GENERIC_NATIVE),
  1.3860 +    JS_FN(js_toSource_str,     str_toSource,          0,0),
  1.3861 +#endif
  1.3862 +
  1.3863 +    /* Java-like methods. */
  1.3864 +    JS_FN(js_toString_str,     js_str_toString,       0,0),
  1.3865 +    JS_FN(js_valueOf_str,      js_str_toString,       0,0),
  1.3866 +    JS_FN("substring",         str_substring,         2,JSFUN_GENERIC_NATIVE),
  1.3867 +    JS_FN("toLowerCase",       str_toLowerCase,       0,JSFUN_GENERIC_NATIVE),
  1.3868 +    JS_FN("toUpperCase",       str_toUpperCase,       0,JSFUN_GENERIC_NATIVE),
  1.3869 +    JS_FN("charAt",            js_str_charAt,         1,JSFUN_GENERIC_NATIVE),
  1.3870 +    JS_FN("charCodeAt",        js_str_charCodeAt,     1,JSFUN_GENERIC_NATIVE),
  1.3871 +    JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0),
  1.3872 +    JS_FN("contains",          str_contains,          1,JSFUN_GENERIC_NATIVE),
  1.3873 +    JS_FN("indexOf",           str_indexOf,           1,JSFUN_GENERIC_NATIVE),
  1.3874 +    JS_FN("lastIndexOf",       str_lastIndexOf,       1,JSFUN_GENERIC_NATIVE),
  1.3875 +    JS_FN("startsWith",        str_startsWith,        1,JSFUN_GENERIC_NATIVE),
  1.3876 +    JS_FN("endsWith",          str_endsWith,          1,JSFUN_GENERIC_NATIVE),
  1.3877 +    JS_FN("trim",              str_trim,              0,JSFUN_GENERIC_NATIVE),
  1.3878 +    JS_FN("trimLeft",          str_trimLeft,          0,JSFUN_GENERIC_NATIVE),
  1.3879 +    JS_FN("trimRight",         str_trimRight,         0,JSFUN_GENERIC_NATIVE),
  1.3880 +    JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE),
  1.3881 +    JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE),
  1.3882 +#if EXPOSE_INTL_API
  1.3883 +    JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
  1.3884 +#else
  1.3885 +    JS_FN("localeCompare",     str_localeCompare,     1,JSFUN_GENERIC_NATIVE),
  1.3886 +#endif
  1.3887 +    JS_SELF_HOSTED_FN("repeat", "String_repeat",      1,0),
  1.3888 +#if EXPOSE_INTL_API
  1.3889 +    JS_FN("normalize",         str_normalize,         0,JSFUN_GENERIC_NATIVE),
  1.3890 +#endif
  1.3891 +
  1.3892 +    /* Perl-ish methods (search is actually Python-esque). */
  1.3893 +    JS_FN("match",             str_match,             1,JSFUN_GENERIC_NATIVE),
  1.3894 +    JS_FN("search",            str_search,            1,JSFUN_GENERIC_NATIVE),
  1.3895 +    JS_FN("replace",           str_replace,           2,JSFUN_GENERIC_NATIVE),
  1.3896 +    JS_FN("split",             str_split,             2,JSFUN_GENERIC_NATIVE),
  1.3897 +    JS_FN("substr",            str_substr,            2,JSFUN_GENERIC_NATIVE),
  1.3898 +
  1.3899 +    /* Python-esque sequence methods. */
  1.3900 +    JS_FN("concat",            str_concat,            1,JSFUN_GENERIC_NATIVE),
  1.3901 +    JS_FN("slice",             str_slice,             2,JSFUN_GENERIC_NATIVE),
  1.3902 +
  1.3903 +    /* HTML string methods. */
  1.3904 +#if JS_HAS_STR_HTML_HELPERS
  1.3905 +    JS_FN("bold",              str_bold,              0,0),
  1.3906 +    JS_FN("italics",           str_italics,           0,0),
  1.3907 +    JS_FN("fixed",             str_fixed,             0,0),
  1.3908 +    JS_FN("fontsize",          str_fontsize,          1,0),
  1.3909 +    JS_FN("fontcolor",         str_fontcolor,         1,0),
  1.3910 +    JS_FN("link",              str_link,              1,0),
  1.3911 +    JS_FN("anchor",            str_anchor,            1,0),
  1.3912 +    JS_FN("strike",            str_strike,            0,0),
  1.3913 +    JS_FN("small",             str_small,             0,0),
  1.3914 +    JS_FN("big",               str_big,               0,0),
  1.3915 +    JS_FN("blink",             str_blink,             0,0),
  1.3916 +    JS_FN("sup",               str_sup,               0,0),
  1.3917 +    JS_FN("sub",               str_sub,               0,0),
  1.3918 +#endif
  1.3919 +    JS_SELF_HOSTED_FN("@@iterator", "String_iterator", 0,0),
  1.3920 +    JS_FS_END
  1.3921 +};
  1.3922 +
  1.3923 +bool
  1.3924 +js_String(JSContext *cx, unsigned argc, Value *vp)
  1.3925 +{
  1.3926 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3927 +
  1.3928 +    RootedString str(cx);
  1.3929 +    if (args.length() > 0) {
  1.3930 +        str = ToString<CanGC>(cx, args[0]);
  1.3931 +        if (!str)
  1.3932 +            return false;
  1.3933 +    } else {
  1.3934 +        str = cx->runtime()->emptyString;
  1.3935 +    }
  1.3936 +
  1.3937 +    if (args.isConstructing()) {
  1.3938 +        StringObject *strobj = StringObject::create(cx, str);
  1.3939 +        if (!strobj)
  1.3940 +            return false;
  1.3941 +        args.rval().setObject(*strobj);
  1.3942 +        return true;
  1.3943 +    }
  1.3944 +
  1.3945 +    args.rval().setString(str);
  1.3946 +    return true;
  1.3947 +}
  1.3948 +
  1.3949 +bool
  1.3950 +js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp)
  1.3951 +{
  1.3952 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.3953 +
  1.3954 +    JS_ASSERT(args.length() <= ARGS_LENGTH_MAX);
  1.3955 +    if (args.length() == 1) {
  1.3956 +        uint16_t code;
  1.3957 +        if (!ToUint16(cx, args[0], &code))
  1.3958 +            return false;
  1.3959 +        if (StaticStrings::hasUnit(code)) {
  1.3960 +            args.rval().setString(cx->staticStrings().getUnit(code));
  1.3961 +            return true;
  1.3962 +        }
  1.3963 +        args[0].setInt32(code);
  1.3964 +    }
  1.3965 +    jschar *chars = cx->pod_malloc<jschar>(args.length() + 1);
  1.3966 +    if (!chars)
  1.3967 +        return false;
  1.3968 +    for (unsigned i = 0; i < args.length(); i++) {
  1.3969 +        uint16_t code;
  1.3970 +        if (!ToUint16(cx, args[i], &code)) {
  1.3971 +            js_free(chars);
  1.3972 +            return false;
  1.3973 +        }
  1.3974 +        chars[i] = (jschar)code;
  1.3975 +    }
  1.3976 +    chars[args.length()] = 0;
  1.3977 +    JSString *str = js_NewString<CanGC>(cx, chars, args.length());
  1.3978 +    if (!str) {
  1.3979 +        js_free(chars);
  1.3980 +        return false;
  1.3981 +    }
  1.3982 +
  1.3983 +    args.rval().setString(str);
  1.3984 +    return true;
  1.3985 +}
  1.3986 +
  1.3987 +static const JSFunctionSpec string_static_methods[] = {
  1.3988 +    JS_FN("fromCharCode", js::str_fromCharCode, 1, 0),
  1.3989 +    JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 0,0),
  1.3990 +
  1.3991 +    // This must be at the end because of bug 853075: functions listed after
  1.3992 +    // self-hosted methods aren't available in self-hosted code.
  1.3993 +#if EXPOSE_INTL_API
  1.3994 +    JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0),
  1.3995 +#endif
  1.3996 +    JS_FS_END
  1.3997 +};
  1.3998 +
  1.3999 +/* static */ Shape *
  1.4000 +StringObject::assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj)
  1.4001 +{
  1.4002 +    JS_ASSERT(obj->nativeEmpty());
  1.4003 +
  1.4004 +    return obj->addDataProperty(cx, cx->names().length, LENGTH_SLOT,
  1.4005 +                                JSPROP_PERMANENT | JSPROP_READONLY);
  1.4006 +}
  1.4007 +
  1.4008 +JSObject *
  1.4009 +js_InitStringClass(JSContext *cx, HandleObject obj)
  1.4010 +{
  1.4011 +    JS_ASSERT(obj->isNative());
  1.4012 +
  1.4013 +    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
  1.4014 +
  1.4015 +    Rooted<JSString*> empty(cx, cx->runtime()->emptyString);
  1.4016 +    RootedObject proto(cx, global->createBlankPrototype(cx, &StringObject::class_));
  1.4017 +    if (!proto || !proto->as<StringObject>().init(cx, empty))
  1.4018 +        return nullptr;
  1.4019 +
  1.4020 +    /* Now create the String function. */
  1.4021 +    RootedFunction ctor(cx);
  1.4022 +    ctor = global->createConstructor(cx, js_String, cx->names().String, 1);
  1.4023 +    if (!ctor)
  1.4024 +        return nullptr;
  1.4025 +
  1.4026 +    if (!LinkConstructorAndPrototype(cx, ctor, proto))
  1.4027 +        return nullptr;
  1.4028 +
  1.4029 +    if (!DefinePropertiesAndBrand(cx, proto, nullptr, string_methods) ||
  1.4030 +        !DefinePropertiesAndBrand(cx, ctor, nullptr, string_static_methods))
  1.4031 +    {
  1.4032 +        return nullptr;
  1.4033 +    }
  1.4034 +
  1.4035 +    if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto))
  1.4036 +        return nullptr;
  1.4037 +
  1.4038 +    /*
  1.4039 +     * Define escape/unescape, the URI encode/decode functions, and maybe
  1.4040 +     * uneval on the global object.
  1.4041 +     */
  1.4042 +    if (!JS_DefineFunctions(cx, global, string_functions))
  1.4043 +        return nullptr;
  1.4044 +
  1.4045 +    return proto;
  1.4046 +}
  1.4047 +
  1.4048 +template <AllowGC allowGC>
  1.4049 +JSFlatString *
  1.4050 +js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length)
  1.4051 +{
  1.4052 +    if (length == 1) {
  1.4053 +        jschar c = chars[0];
  1.4054 +        if (StaticStrings::hasUnit(c)) {
  1.4055 +            // Free |chars| because we're taking possession of it, but it's no
  1.4056 +            // longer needed because we use the static string instead.
  1.4057 +            js_free(chars);
  1.4058 +            return cx->staticStrings().getUnit(c);
  1.4059 +        }
  1.4060 +    }
  1.4061 +
  1.4062 +    return JSFlatString::new_<allowGC>(cx, chars, length);
  1.4063 +}
  1.4064 +
  1.4065 +template JSFlatString *
  1.4066 +js_NewString<CanGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
  1.4067 +
  1.4068 +template JSFlatString *
  1.4069 +js_NewString<NoGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
  1.4070 +
  1.4071 +JSLinearString *
  1.4072 +js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length)
  1.4073 +{
  1.4074 +    if (length == 0)
  1.4075 +        return cx->emptyString();
  1.4076 +
  1.4077 +    JSLinearString *base = baseArg->ensureLinear(cx);
  1.4078 +    if (!base)
  1.4079 +        return nullptr;
  1.4080 +
  1.4081 +    if (start == 0 && length == base->length())
  1.4082 +        return base;
  1.4083 +
  1.4084 +    const jschar *chars = base->chars() + start;
  1.4085 +
  1.4086 +    if (JSLinearString *staticStr = cx->staticStrings().lookup(chars, length))
  1.4087 +        return staticStr;
  1.4088 +
  1.4089 +    return JSDependentString::new_(cx, base, chars, length);
  1.4090 +}
  1.4091 +
  1.4092 +template <AllowGC allowGC>
  1.4093 +JSFlatString *
  1.4094 +js_NewStringCopyN(ExclusiveContext *cx, const jschar *s, size_t n)
  1.4095 +{
  1.4096 +    if (JSFatInlineString::lengthFits(n))
  1.4097 +        return NewFatInlineString<allowGC>(cx, TwoByteChars(s, n));
  1.4098 +
  1.4099 +    jschar *news = cx->pod_malloc<jschar>(n + 1);
  1.4100 +    if (!news)
  1.4101 +        return nullptr;
  1.4102 +    js_strncpy(news, s, n);
  1.4103 +    news[n] = 0;
  1.4104 +    JSFlatString *str = js_NewString<allowGC>(cx, news, n);
  1.4105 +    if (!str)
  1.4106 +        js_free(news);
  1.4107 +    return str;
  1.4108 +}
  1.4109 +
  1.4110 +template JSFlatString *
  1.4111 +js_NewStringCopyN<CanGC>(ExclusiveContext *cx, const jschar *s, size_t n);
  1.4112 +
  1.4113 +template JSFlatString *
  1.4114 +js_NewStringCopyN<NoGC>(ExclusiveContext *cx, const jschar *s, size_t n);
  1.4115 +
  1.4116 +template <AllowGC allowGC>
  1.4117 +JSFlatString *
  1.4118 +js_NewStringCopyN(ThreadSafeContext *cx, const char *s, size_t n)
  1.4119 +{
  1.4120 +    if (JSFatInlineString::lengthFits(n))
  1.4121 +        return NewFatInlineString<allowGC>(cx, JS::Latin1Chars(s, n));
  1.4122 +
  1.4123 +    jschar *chars = InflateString(cx, s, &n);
  1.4124 +    if (!chars)
  1.4125 +        return nullptr;
  1.4126 +    JSFlatString *str = js_NewString<allowGC>(cx, chars, n);
  1.4127 +    if (!str)
  1.4128 +        js_free(chars);
  1.4129 +    return str;
  1.4130 +}
  1.4131 +
  1.4132 +template JSFlatString *
  1.4133 +js_NewStringCopyN<CanGC>(ThreadSafeContext *cx, const char *s, size_t n);
  1.4134 +
  1.4135 +template JSFlatString *
  1.4136 +js_NewStringCopyN<NoGC>(ThreadSafeContext *cx, const char *s, size_t n);
  1.4137 +
  1.4138 +template <AllowGC allowGC>
  1.4139 +JSFlatString *
  1.4140 +js_NewStringCopyZ(ExclusiveContext *cx, const jschar *s)
  1.4141 +{
  1.4142 +    size_t n = js_strlen(s);
  1.4143 +    if (JSFatInlineString::lengthFits(n))
  1.4144 +        return NewFatInlineString<allowGC>(cx, TwoByteChars(s, n));
  1.4145 +
  1.4146 +    size_t m = (n + 1) * sizeof(jschar);
  1.4147 +    jschar *news = (jschar *) cx->malloc_(m);
  1.4148 +    if (!news)
  1.4149 +        return nullptr;
  1.4150 +    js_memcpy(news, s, m);
  1.4151 +    JSFlatString *str = js_NewString<allowGC>(cx, news, n);
  1.4152 +    if (!str)
  1.4153 +        js_free(news);
  1.4154 +    return str;
  1.4155 +}
  1.4156 +
  1.4157 +template JSFlatString *
  1.4158 +js_NewStringCopyZ<CanGC>(ExclusiveContext *cx, const jschar *s);
  1.4159 +
  1.4160 +template JSFlatString *
  1.4161 +js_NewStringCopyZ<NoGC>(ExclusiveContext *cx, const jschar *s);
  1.4162 +
  1.4163 +template <AllowGC allowGC>
  1.4164 +JSFlatString *
  1.4165 +js_NewStringCopyZ(ThreadSafeContext *cx, const char *s)
  1.4166 +{
  1.4167 +    return js_NewStringCopyN<allowGC>(cx, s, strlen(s));
  1.4168 +}
  1.4169 +
  1.4170 +template JSFlatString *
  1.4171 +js_NewStringCopyZ<CanGC>(ThreadSafeContext *cx, const char *s);
  1.4172 +
  1.4173 +template JSFlatString *
  1.4174 +js_NewStringCopyZ<NoGC>(ThreadSafeContext *cx, const char *s);
  1.4175 +
  1.4176 +const char *
  1.4177 +js_ValueToPrintable(JSContext *cx, const Value &vArg, JSAutoByteString *bytes, bool asSource)
  1.4178 +{
  1.4179 +    RootedValue v(cx, vArg);
  1.4180 +    JSString *str;
  1.4181 +    if (asSource)
  1.4182 +        str = ValueToSource(cx, v);
  1.4183 +    else
  1.4184 +        str = ToString<CanGC>(cx, v);
  1.4185 +    if (!str)
  1.4186 +        return nullptr;
  1.4187 +    str = js_QuoteString(cx, str, 0);
  1.4188 +    if (!str)
  1.4189 +        return nullptr;
  1.4190 +    return bytes->encodeLatin1(cx, str);
  1.4191 +}
  1.4192 +
  1.4193 +template <AllowGC allowGC>
  1.4194 +JSString *
  1.4195 +js::ToStringSlow(ExclusiveContext *cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
  1.4196 +{
  1.4197 +    /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
  1.4198 +    JS_ASSERT(!arg.isString());
  1.4199 +
  1.4200 +    Value v = arg;
  1.4201 +    if (!v.isPrimitive()) {
  1.4202 +        if (!cx->shouldBeJSContext() || !allowGC)
  1.4203 +            return nullptr;
  1.4204 +        RootedValue v2(cx, v);
  1.4205 +        if (!ToPrimitive(cx->asJSContext(), JSTYPE_STRING, &v2))
  1.4206 +            return nullptr;
  1.4207 +        v = v2;
  1.4208 +    }
  1.4209 +
  1.4210 +    JSString *str;
  1.4211 +    if (v.isString()) {
  1.4212 +        str = v.toString();
  1.4213 +    } else if (v.isInt32()) {
  1.4214 +        str = Int32ToString<allowGC>(cx, v.toInt32());
  1.4215 +    } else if (v.isDouble()) {
  1.4216 +        str = NumberToString<allowGC>(cx, v.toDouble());
  1.4217 +    } else if (v.isBoolean()) {
  1.4218 +        str = js_BooleanToString(cx, v.toBoolean());
  1.4219 +    } else if (v.isNull()) {
  1.4220 +        str = cx->names().null;
  1.4221 +    } else {
  1.4222 +        str = cx->names().undefined;
  1.4223 +    }
  1.4224 +    return str;
  1.4225 +}
  1.4226 +
  1.4227 +template JSString *
  1.4228 +js::ToStringSlow<CanGC>(ExclusiveContext *cx, HandleValue arg);
  1.4229 +
  1.4230 +template JSString *
  1.4231 +js::ToStringSlow<NoGC>(ExclusiveContext *cx, Value arg);
  1.4232 +
  1.4233 +JS_PUBLIC_API(JSString *)
  1.4234 +js::ToStringSlow(JSContext *cx, HandleValue v)
  1.4235 +{
  1.4236 +    return ToStringSlow<CanGC>(cx, v);
  1.4237 +}
  1.4238 +
  1.4239 +JSString *
  1.4240 +js::ValueToSource(JSContext *cx, HandleValue v)
  1.4241 +{
  1.4242 +    JS_CHECK_RECURSION(cx, return nullptr);
  1.4243 +    assertSameCompartment(cx, v);
  1.4244 +
  1.4245 +    if (v.isUndefined())
  1.4246 +        return cx->names().void0;
  1.4247 +    if (v.isString())
  1.4248 +        return StringToSource(cx, v.toString());
  1.4249 +    if (v.isPrimitive()) {
  1.4250 +        /* Special case to preserve negative zero, _contra_ toString. */
  1.4251 +        if (v.isDouble() && IsNegativeZero(v.toDouble())) {
  1.4252 +            /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
  1.4253 +            static const jschar js_negzero_ucNstr[] = {'-', '0'};
  1.4254 +
  1.4255 +            return js_NewStringCopyN<CanGC>(cx, js_negzero_ucNstr, 2);
  1.4256 +        }
  1.4257 +        return ToString<CanGC>(cx, v);
  1.4258 +    }
  1.4259 +
  1.4260 +    RootedValue fval(cx);
  1.4261 +    RootedObject obj(cx, &v.toObject());
  1.4262 +    if (!JSObject::getProperty(cx, obj, obj, cx->names().toSource, &fval))
  1.4263 +        return nullptr;
  1.4264 +    if (js_IsCallable(fval)) {
  1.4265 +        RootedValue rval(cx);
  1.4266 +        if (!Invoke(cx, ObjectValue(*obj), fval, 0, nullptr, &rval))
  1.4267 +            return nullptr;
  1.4268 +        return ToString<CanGC>(cx, rval);
  1.4269 +    }
  1.4270 +
  1.4271 +    return ObjectToSource(cx, obj);
  1.4272 +}
  1.4273 +
  1.4274 +JSString *
  1.4275 +js::StringToSource(JSContext *cx, JSString *str)
  1.4276 +{
  1.4277 +    return js_QuoteString(cx, str, '"');
  1.4278 +}
  1.4279 +
  1.4280 +bool
  1.4281 +js::EqualStrings(JSContext *cx, JSString *str1, JSString *str2, bool *result)
  1.4282 +{
  1.4283 +    if (str1 == str2) {
  1.4284 +        *result = true;
  1.4285 +        return true;
  1.4286 +    }
  1.4287 +
  1.4288 +    size_t length1 = str1->length();
  1.4289 +    if (length1 != str2->length()) {
  1.4290 +        *result = false;
  1.4291 +        return true;
  1.4292 +    }
  1.4293 +
  1.4294 +    JSLinearString *linear1 = str1->ensureLinear(cx);
  1.4295 +    if (!linear1)
  1.4296 +        return false;
  1.4297 +    JSLinearString *linear2 = str2->ensureLinear(cx);
  1.4298 +    if (!linear2)
  1.4299 +        return false;
  1.4300 +
  1.4301 +    *result = PodEqual(linear1->chars(), linear2->chars(), length1);
  1.4302 +    return true;
  1.4303 +}
  1.4304 +
  1.4305 +bool
  1.4306 +js::EqualStrings(JSLinearString *str1, JSLinearString *str2)
  1.4307 +{
  1.4308 +    if (str1 == str2)
  1.4309 +        return true;
  1.4310 +
  1.4311 +    size_t length1 = str1->length();
  1.4312 +    if (length1 != str2->length())
  1.4313 +        return false;
  1.4314 +
  1.4315 +    return PodEqual(str1->chars(), str2->chars(), length1);
  1.4316 +}
  1.4317 +
  1.4318 +static bool
  1.4319 +CompareStringsImpl(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
  1.4320 +{
  1.4321 +    JS_ASSERT(str1);
  1.4322 +    JS_ASSERT(str2);
  1.4323 +
  1.4324 +    if (str1 == str2) {
  1.4325 +        *result = 0;
  1.4326 +        return true;
  1.4327 +    }
  1.4328 +
  1.4329 +    const jschar *s1 = str1->getChars(cx);
  1.4330 +    if (!s1)
  1.4331 +        return false;
  1.4332 +
  1.4333 +    const jschar *s2 = str2->getChars(cx);
  1.4334 +    if (!s2)
  1.4335 +        return false;
  1.4336 +
  1.4337 +    *result = CompareChars(s1, str1->length(), s2, str2->length());
  1.4338 +    return true;
  1.4339 +}
  1.4340 +
  1.4341 +bool
  1.4342 +js::CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result)
  1.4343 +{
  1.4344 +    return CompareStringsImpl(cx, str1, str2, result);
  1.4345 +}
  1.4346 +
  1.4347 +int32_t
  1.4348 +js::CompareAtoms(JSAtom *atom1, JSAtom *atom2)
  1.4349 +{
  1.4350 +    return CompareChars(atom1->chars(), atom1->length(), atom2->chars(), atom2->length());
  1.4351 +}
  1.4352 +
  1.4353 +bool
  1.4354 +js::StringEqualsAscii(JSLinearString *str, const char *asciiBytes)
  1.4355 +{
  1.4356 +    size_t length = strlen(asciiBytes);
  1.4357 +#ifdef DEBUG
  1.4358 +    for (size_t i = 0; i != length; ++i)
  1.4359 +        JS_ASSERT(unsigned(asciiBytes[i]) <= 127);
  1.4360 +#endif
  1.4361 +    if (length != str->length())
  1.4362 +        return false;
  1.4363 +    const jschar *chars = str->chars();
  1.4364 +    for (size_t i = 0; i != length; ++i) {
  1.4365 +        if (unsigned(asciiBytes[i]) != unsigned(chars[i]))
  1.4366 +            return false;
  1.4367 +    }
  1.4368 +    return true;
  1.4369 +}
  1.4370 +
  1.4371 +size_t
  1.4372 +js_strlen(const jschar *s)
  1.4373 +{
  1.4374 +    const jschar *t;
  1.4375 +
  1.4376 +    for (t = s; *t != 0; t++)
  1.4377 +        continue;
  1.4378 +    return (size_t)(t - s);
  1.4379 +}
  1.4380 +
  1.4381 +int32_t
  1.4382 +js_strcmp(const jschar *lhs, const jschar *rhs)
  1.4383 +{
  1.4384 +    while (true) {
  1.4385 +        if (*lhs != *rhs)
  1.4386 +            return int32_t(*lhs) - int32_t(*rhs);
  1.4387 +        if (*lhs == 0)
  1.4388 +            return 0;
  1.4389 +        ++lhs, ++rhs;
  1.4390 +    }
  1.4391 +}
  1.4392 +
  1.4393 +jschar *
  1.4394 +js_strdup(js::ThreadSafeContext *cx, const jschar *s)
  1.4395 +{
  1.4396 +    size_t n = js_strlen(s);
  1.4397 +    jschar *ret = cx->pod_malloc<jschar>(n + 1);
  1.4398 +    if (!ret)
  1.4399 +        return nullptr;
  1.4400 +    js_strncpy(ret, s, n);
  1.4401 +    ret[n] = '\0';
  1.4402 +    return ret;
  1.4403 +}
  1.4404 +
  1.4405 +jschar *
  1.4406 +js_strchr_limit(const jschar *s, jschar c, const jschar *limit)
  1.4407 +{
  1.4408 +    while (s < limit) {
  1.4409 +        if (*s == c)
  1.4410 +            return (jschar *)s;
  1.4411 +        s++;
  1.4412 +    }
  1.4413 +    return nullptr;
  1.4414 +}
  1.4415 +
  1.4416 +jschar *
  1.4417 +js::InflateString(ThreadSafeContext *cx, const char *bytes, size_t *lengthp)
  1.4418 +{
  1.4419 +    size_t nchars;
  1.4420 +    jschar *chars;
  1.4421 +    size_t nbytes = *lengthp;
  1.4422 +
  1.4423 +    nchars = nbytes;
  1.4424 +    chars = cx->pod_malloc<jschar>(nchars + 1);
  1.4425 +    if (!chars)
  1.4426 +        goto bad;
  1.4427 +    for (size_t i = 0; i < nchars; i++)
  1.4428 +        chars[i] = (unsigned char) bytes[i];
  1.4429 +    *lengthp = nchars;
  1.4430 +    chars[nchars] = 0;
  1.4431 +    return chars;
  1.4432 +
  1.4433 +  bad:
  1.4434 +    // For compatibility with callers of JS_DecodeBytes we must zero lengthp
  1.4435 +    // on errors.
  1.4436 +    *lengthp = 0;
  1.4437 +    return nullptr;
  1.4438 +}
  1.4439 +
  1.4440 +bool
  1.4441 +js::DeflateStringToBuffer(JSContext *maybecx, const jschar *src, size_t srclen,
  1.4442 +                          char *dst, size_t *dstlenp)
  1.4443 +{
  1.4444 +    size_t dstlen = *dstlenp;
  1.4445 +    if (srclen > dstlen) {
  1.4446 +        for (size_t i = 0; i < dstlen; i++)
  1.4447 +            dst[i] = (char) src[i];
  1.4448 +        if (maybecx) {
  1.4449 +            AutoSuppressGC suppress(maybecx);
  1.4450 +            JS_ReportErrorNumber(maybecx, js_GetErrorMessage, nullptr,
  1.4451 +                                 JSMSG_BUFFER_TOO_SMALL);
  1.4452 +        }
  1.4453 +        return false;
  1.4454 +    }
  1.4455 +    for (size_t i = 0; i < srclen; i++)
  1.4456 +        dst[i] = (char) src[i];
  1.4457 +    *dstlenp = srclen;
  1.4458 +    return true;
  1.4459 +}
  1.4460 +
  1.4461 +#define ____ false
  1.4462 +
  1.4463 +/*
  1.4464 + * Identifier start chars:
  1.4465 + * -      36:    $
  1.4466 + * -  65..90: A..Z
  1.4467 + * -      95:    _
  1.4468 + * - 97..122: a..z
  1.4469 + */
  1.4470 +const bool js_isidstart[] = {
  1.4471 +/*       0     1     2     3     4     5     6     7     8     9  */
  1.4472 +/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4473 +/*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4474 +/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4475 +/*  3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
  1.4476 +/*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4477 +/*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4478 +/*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
  1.4479 +/*  7 */ true, true, true, true, true, true, true, true, true, true,
  1.4480 +/*  8 */ true, true, true, true, true, true, true, true, true, true,
  1.4481 +/*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
  1.4482 +/* 10 */ true, true, true, true, true, true, true, true, true, true,
  1.4483 +/* 11 */ true, true, true, true, true, true, true, true, true, true,
  1.4484 +/* 12 */ true, true, true, ____, ____, ____, ____, ____
  1.4485 +};
  1.4486 +
  1.4487 +/*
  1.4488 + * Identifier chars:
  1.4489 + * -      36:    $
  1.4490 + * -  48..57: 0..9
  1.4491 + * -  65..90: A..Z
  1.4492 + * -      95:    _
  1.4493 + * - 97..122: a..z
  1.4494 + */
  1.4495 +const bool js_isident[] = {
  1.4496 +/*       0     1     2     3     4     5     6     7     8     9  */
  1.4497 +/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4498 +/*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4499 +/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4500 +/*  3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
  1.4501 +/*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
  1.4502 +/*  5 */ true, true, true, true, true, true, true, true, ____, ____,
  1.4503 +/*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
  1.4504 +/*  7 */ true, true, true, true, true, true, true, true, true, true,
  1.4505 +/*  8 */ true, true, true, true, true, true, true, true, true, true,
  1.4506 +/*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
  1.4507 +/* 10 */ true, true, true, true, true, true, true, true, true, true,
  1.4508 +/* 11 */ true, true, true, true, true, true, true, true, true, true,
  1.4509 +/* 12 */ true, true, true, ____, ____, ____, ____, ____
  1.4510 +};
  1.4511 +
  1.4512 +/* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
  1.4513 +const bool js_isspace[] = {
  1.4514 +/*       0     1     2     3     4     5     6     7     8     9  */
  1.4515 +/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
  1.4516 +/*  1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
  1.4517 +/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4518 +/*  3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
  1.4519 +/*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4520 +/*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4521 +/*  6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4522 +/*  7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4523 +/*  8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4524 +/*  9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4525 +/* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4526 +/* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4527 +/* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
  1.4528 +};
  1.4529 +
  1.4530 +/*
  1.4531 + * Uri reserved chars + #:
  1.4532 + * - 35: #
  1.4533 + * - 36: $
  1.4534 + * - 38: &
  1.4535 + * - 43: +
  1.4536 + * - 44: ,
  1.4537 + * - 47: /
  1.4538 + * - 58: :
  1.4539 + * - 59: ;
  1.4540 + * - 61: =
  1.4541 + * - 63: ?
  1.4542 + * - 64: @
  1.4543 + */
  1.4544 +static const bool js_isUriReservedPlusPound[] = {
  1.4545 +/*       0     1     2     3     4     5     6     7     8     9  */
  1.4546 +/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4547 +/*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4548 +/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4549 +/*  3 */ ____, ____, ____, ____, ____, true, true, ____, true, ____,
  1.4550 +/*  4 */ ____, ____, ____, true, true, ____, ____, true, ____, ____,
  1.4551 +/*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
  1.4552 +/*  6 */ ____, true, ____, true, true, ____, ____, ____, ____, ____,
  1.4553 +/*  7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4554 +/*  8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4555 +/*  9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4556 +/* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4557 +/* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4558 +/* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
  1.4559 +};
  1.4560 +
  1.4561 +/*
  1.4562 + * Uri unescaped chars:
  1.4563 + * -      33: !
  1.4564 + * -      39: '
  1.4565 + * -      40: (
  1.4566 + * -      41: )
  1.4567 + * -      42: *
  1.4568 + * -      45: -
  1.4569 + * -      46: .
  1.4570 + * -  48..57: 0-9
  1.4571 + * -  65..90: A-Z
  1.4572 + * -      95: _
  1.4573 + * - 97..122: a-z
  1.4574 + * -     126: ~
  1.4575 + */
  1.4576 +static const bool js_isUriUnescaped[] = {
  1.4577 +/*       0     1     2     3     4     5     6     7     8     9  */
  1.4578 +/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4579 +/*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4580 +/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
  1.4581 +/*  3 */ ____, ____, ____, true, ____, ____, ____, ____, ____, true,
  1.4582 +/*  4 */ true, true, true, ____, ____, true, true, ____, true, true,
  1.4583 +/*  5 */ true, true, true, true, true, true, true, true, ____, ____,
  1.4584 +/*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
  1.4585 +/*  7 */ true, true, true, true, true, true, true, true, true, true,
  1.4586 +/*  8 */ true, true, true, true, true, true, true, true, true, true,
  1.4587 +/*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
  1.4588 +/* 10 */ true, true, true, true, true, true, true, true, true, true,
  1.4589 +/* 11 */ true, true, true, true, true, true, true, true, true, true,
  1.4590 +/* 12 */ true, true, true, ____, ____, ____, true, ____
  1.4591 +};
  1.4592 +
  1.4593 +#undef ____
  1.4594 +
  1.4595 +#define URI_CHUNK 64U
  1.4596 +
  1.4597 +static inline bool
  1.4598 +TransferBufferToString(StringBuffer &sb, MutableHandleValue rval)
  1.4599 +{
  1.4600 +    JSString *str = sb.finishString();
  1.4601 +    if (!str)
  1.4602 +        return false;
  1.4603 +    rval.setString(str);
  1.4604 +    return true;
  1.4605 +}
  1.4606 +
  1.4607 +/*
  1.4608 + * ECMA 3, 15.1.3 URI Handling Function Properties
  1.4609 + *
  1.4610 + * The following are implementations of the algorithms
  1.4611 + * given in the ECMA specification for the hidden functions
  1.4612 + * 'Encode' and 'Decode'.
  1.4613 + */
  1.4614 +static bool
  1.4615 +Encode(JSContext *cx, Handle<JSLinearString*> str, const bool *unescapedSet,
  1.4616 +       const bool *unescapedSet2, MutableHandleValue rval)
  1.4617 +{
  1.4618 +    static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
  1.4619 +
  1.4620 +    size_t length = str->length();
  1.4621 +    if (length == 0) {
  1.4622 +        rval.setString(cx->runtime()->emptyString);
  1.4623 +        return true;
  1.4624 +    }
  1.4625 +
  1.4626 +    const jschar *chars = str->chars();
  1.4627 +    StringBuffer sb(cx);
  1.4628 +    if (!sb.reserve(length))
  1.4629 +        return false;
  1.4630 +    jschar hexBuf[4];
  1.4631 +    hexBuf[0] = '%';
  1.4632 +    hexBuf[3] = 0;
  1.4633 +    for (size_t k = 0; k < length; k++) {
  1.4634 +        jschar c = chars[k];
  1.4635 +        if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) {
  1.4636 +            if (!sb.append(c))
  1.4637 +                return false;
  1.4638 +        } else {
  1.4639 +            if ((c >= 0xDC00) && (c <= 0xDFFF)) {
  1.4640 +                JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI, nullptr);
  1.4641 +                return false;
  1.4642 +            }
  1.4643 +            uint32_t v;
  1.4644 +            if (c < 0xD800 || c > 0xDBFF) {
  1.4645 +                v = c;
  1.4646 +            } else {
  1.4647 +                k++;
  1.4648 +                if (k == length) {
  1.4649 +                    JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  1.4650 +                                     JSMSG_BAD_URI, nullptr);
  1.4651 +                    return false;
  1.4652 +                }
  1.4653 +                jschar c2 = chars[k];
  1.4654 +                if ((c2 < 0xDC00) || (c2 > 0xDFFF)) {
  1.4655 +                    JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  1.4656 +                                     JSMSG_BAD_URI, nullptr);
  1.4657 +                    return false;
  1.4658 +                }
  1.4659 +                v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
  1.4660 +            }
  1.4661 +            uint8_t utf8buf[4];
  1.4662 +            size_t L = js_OneUcs4ToUtf8Char(utf8buf, v);
  1.4663 +            for (size_t j = 0; j < L; j++) {
  1.4664 +                hexBuf[1] = HexDigits[utf8buf[j] >> 4];
  1.4665 +                hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
  1.4666 +                if (!sb.append(hexBuf, 3))
  1.4667 +                    return false;
  1.4668 +            }
  1.4669 +        }
  1.4670 +    }
  1.4671 +
  1.4672 +    return TransferBufferToString(sb, rval);
  1.4673 +}
  1.4674 +
  1.4675 +static bool
  1.4676 +Decode(JSContext *cx, Handle<JSLinearString*> str, const bool *reservedSet, MutableHandleValue rval)
  1.4677 +{
  1.4678 +    size_t length = str->length();
  1.4679 +    if (length == 0) {
  1.4680 +        rval.setString(cx->runtime()->emptyString);
  1.4681 +        return true;
  1.4682 +    }
  1.4683 +
  1.4684 +    const jschar *chars = str->chars();
  1.4685 +    StringBuffer sb(cx);
  1.4686 +    for (size_t k = 0; k < length; k++) {
  1.4687 +        jschar c = chars[k];
  1.4688 +        if (c == '%') {
  1.4689 +            size_t start = k;
  1.4690 +            if ((k + 2) >= length)
  1.4691 +                goto report_bad_uri;
  1.4692 +            if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
  1.4693 +                goto report_bad_uri;
  1.4694 +            uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
  1.4695 +            k += 2;
  1.4696 +            if (!(B & 0x80)) {
  1.4697 +                c = (jschar)B;
  1.4698 +            } else {
  1.4699 +                int n = 1;
  1.4700 +                while (B & (0x80 >> n))
  1.4701 +                    n++;
  1.4702 +                if (n == 1 || n > 4)
  1.4703 +                    goto report_bad_uri;
  1.4704 +                uint8_t octets[4];
  1.4705 +                octets[0] = (uint8_t)B;
  1.4706 +                if (k + 3 * (n - 1) >= length)
  1.4707 +                    goto report_bad_uri;
  1.4708 +                for (int j = 1; j < n; j++) {
  1.4709 +                    k++;
  1.4710 +                    if (chars[k] != '%')
  1.4711 +                        goto report_bad_uri;
  1.4712 +                    if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
  1.4713 +                        goto report_bad_uri;
  1.4714 +                    B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
  1.4715 +                    if ((B & 0xC0) != 0x80)
  1.4716 +                        goto report_bad_uri;
  1.4717 +                    k += 2;
  1.4718 +                    octets[j] = (char)B;
  1.4719 +                }
  1.4720 +                uint32_t v = JS::Utf8ToOneUcs4Char(octets, n);
  1.4721 +                if (v >= 0x10000) {
  1.4722 +                    v -= 0x10000;
  1.4723 +                    if (v > 0xFFFFF)
  1.4724 +                        goto report_bad_uri;
  1.4725 +                    c = (jschar)((v & 0x3FF) + 0xDC00);
  1.4726 +                    jschar H = (jschar)((v >> 10) + 0xD800);
  1.4727 +                    if (!sb.append(H))
  1.4728 +                        return false;
  1.4729 +                } else {
  1.4730 +                    c = (jschar)v;
  1.4731 +                }
  1.4732 +            }
  1.4733 +            if (c < 128 && reservedSet && reservedSet[c]) {
  1.4734 +                if (!sb.append(chars + start, k - start + 1))
  1.4735 +                    return false;
  1.4736 +            } else {
  1.4737 +                if (!sb.append(c))
  1.4738 +                    return false;
  1.4739 +            }
  1.4740 +        } else {
  1.4741 +            if (!sb.append(c))
  1.4742 +                return false;
  1.4743 +        }
  1.4744 +    }
  1.4745 +
  1.4746 +    return TransferBufferToString(sb, rval);
  1.4747 +
  1.4748 +  report_bad_uri:
  1.4749 +    JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI);
  1.4750 +    /* FALL THROUGH */
  1.4751 +
  1.4752 +    return false;
  1.4753 +}
  1.4754 +
  1.4755 +static bool
  1.4756 +str_decodeURI(JSContext *cx, unsigned argc, Value *vp)
  1.4757 +{
  1.4758 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.4759 +    Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
  1.4760 +    if (!str)
  1.4761 +        return false;
  1.4762 +
  1.4763 +    return Decode(cx, str, js_isUriReservedPlusPound, args.rval());
  1.4764 +}
  1.4765 +
  1.4766 +static bool
  1.4767 +str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
  1.4768 +{
  1.4769 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.4770 +    Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
  1.4771 +    if (!str)
  1.4772 +        return false;
  1.4773 +
  1.4774 +    return Decode(cx, str, nullptr, args.rval());
  1.4775 +}
  1.4776 +
  1.4777 +static bool
  1.4778 +str_encodeURI(JSContext *cx, unsigned argc, Value *vp)
  1.4779 +{
  1.4780 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.4781 +    Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
  1.4782 +    if (!str)
  1.4783 +        return false;
  1.4784 +
  1.4785 +    return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval());
  1.4786 +}
  1.4787 +
  1.4788 +static bool
  1.4789 +str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp)
  1.4790 +{
  1.4791 +    CallArgs args = CallArgsFromVp(argc, vp);
  1.4792 +    Rooted<JSLinearString*> str(cx, ArgToRootedString(cx, args, 0));
  1.4793 +    if (!str)
  1.4794 +        return false;
  1.4795 +
  1.4796 +    return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval());
  1.4797 +}
  1.4798 +
  1.4799 +/*
  1.4800 + * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
  1.4801 + * least 4 bytes long.  Return the number of UTF-8 bytes of data written.
  1.4802 + */
  1.4803 +int
  1.4804 +js_OneUcs4ToUtf8Char(uint8_t *utf8Buffer, uint32_t ucs4Char)
  1.4805 +{
  1.4806 +    int utf8Length = 1;
  1.4807 +
  1.4808 +    JS_ASSERT(ucs4Char <= 0x10FFFF);
  1.4809 +    if (ucs4Char < 0x80) {
  1.4810 +        *utf8Buffer = (uint8_t)ucs4Char;
  1.4811 +    } else {
  1.4812 +        int i;
  1.4813 +        uint32_t a = ucs4Char >> 11;
  1.4814 +        utf8Length = 2;
  1.4815 +        while (a) {
  1.4816 +            a >>= 5;
  1.4817 +            utf8Length++;
  1.4818 +        }
  1.4819 +        i = utf8Length;
  1.4820 +        while (--i) {
  1.4821 +            utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80);
  1.4822 +            ucs4Char >>= 6;
  1.4823 +        }
  1.4824 +        *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
  1.4825 +    }
  1.4826 +    return utf8Length;
  1.4827 +}
  1.4828 +
  1.4829 +size_t
  1.4830 +js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, JSLinearString *str,
  1.4831 +                         uint32_t quote)
  1.4832 +{
  1.4833 +    return PutEscapedStringImpl(buffer, bufferSize, fp, str->chars(),
  1.4834 +                                str->length(), quote);
  1.4835 +}
  1.4836 +
  1.4837 +size_t
  1.4838 +js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const jschar *chars,
  1.4839 +                         size_t length, uint32_t quote)
  1.4840 +{
  1.4841 +    enum {
  1.4842 +        STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
  1.4843 +    } state;
  1.4844 +
  1.4845 +    JS_ASSERT(quote == 0 || quote == '\'' || quote == '"');
  1.4846 +    JS_ASSERT_IF(!buffer, bufferSize == 0);
  1.4847 +    JS_ASSERT_IF(fp, !buffer);
  1.4848 +
  1.4849 +    if (bufferSize == 0)
  1.4850 +        buffer = nullptr;
  1.4851 +    else
  1.4852 +        bufferSize--;
  1.4853 +
  1.4854 +    const jschar *charsEnd = chars + length;
  1.4855 +    size_t n = 0;
  1.4856 +    state = FIRST_QUOTE;
  1.4857 +    unsigned shift = 0;
  1.4858 +    unsigned hex = 0;
  1.4859 +    unsigned u = 0;
  1.4860 +    char c = 0;  /* to quell GCC warnings */
  1.4861 +
  1.4862 +    for (;;) {
  1.4863 +        switch (state) {
  1.4864 +          case STOP:
  1.4865 +            goto stop;
  1.4866 +          case FIRST_QUOTE:
  1.4867 +            state = CHARS;
  1.4868 +            goto do_quote;
  1.4869 +          case LAST_QUOTE:
  1.4870 +            state = STOP;
  1.4871 +          do_quote:
  1.4872 +            if (quote == 0)
  1.4873 +                continue;
  1.4874 +            c = (char)quote;
  1.4875 +            break;
  1.4876 +          case CHARS:
  1.4877 +            if (chars == charsEnd) {
  1.4878 +                state = LAST_QUOTE;
  1.4879 +                continue;
  1.4880 +            }
  1.4881 +            u = *chars++;
  1.4882 +            if (u < ' ') {
  1.4883 +                if (u != 0) {
  1.4884 +                    const char *escape = strchr(js_EscapeMap, (int)u);
  1.4885 +                    if (escape) {
  1.4886 +                        u = escape[1];
  1.4887 +                        goto do_escape;
  1.4888 +                    }
  1.4889 +                }
  1.4890 +                goto do_hex_escape;
  1.4891 +            }
  1.4892 +            if (u < 127) {
  1.4893 +                if (u == quote || u == '\\')
  1.4894 +                    goto do_escape;
  1.4895 +                c = (char)u;
  1.4896 +            } else if (u < 0x100) {
  1.4897 +                goto do_hex_escape;
  1.4898 +            } else {
  1.4899 +                shift = 16;
  1.4900 +                hex = u;
  1.4901 +                u = 'u';
  1.4902 +                goto do_escape;
  1.4903 +            }
  1.4904 +            break;
  1.4905 +          do_hex_escape:
  1.4906 +            shift = 8;
  1.4907 +            hex = u;
  1.4908 +            u = 'x';
  1.4909 +          do_escape:
  1.4910 +            c = '\\';
  1.4911 +            state = ESCAPE_START;
  1.4912 +            break;
  1.4913 +          case ESCAPE_START:
  1.4914 +            JS_ASSERT(' ' <= u && u < 127);
  1.4915 +            c = (char)u;
  1.4916 +            state = ESCAPE_MORE;
  1.4917 +            break;
  1.4918 +          case ESCAPE_MORE:
  1.4919 +            if (shift == 0) {
  1.4920 +                state = CHARS;
  1.4921 +                continue;
  1.4922 +            }
  1.4923 +            shift -= 4;
  1.4924 +            u = 0xF & (hex >> shift);
  1.4925 +            c = (char)(u + (u < 10 ? '0' : 'A' - 10));
  1.4926 +            break;
  1.4927 +        }
  1.4928 +        if (buffer) {
  1.4929 +            JS_ASSERT(n <= bufferSize);
  1.4930 +            if (n != bufferSize) {
  1.4931 +                buffer[n] = c;
  1.4932 +            } else {
  1.4933 +                buffer[n] = '\0';
  1.4934 +                buffer = nullptr;
  1.4935 +            }
  1.4936 +        } else if (fp) {
  1.4937 +            if (fputc(c, fp) < 0)
  1.4938 +                return size_t(-1);
  1.4939 +        }
  1.4940 +        n++;
  1.4941 +    }
  1.4942 +  stop:
  1.4943 +    if (buffer)
  1.4944 +        buffer[n] = '\0';
  1.4945 +    return n;
  1.4946 +}

mercurial