PageRenderTime 226ms CodeModel.GetById 15ms app.highlight 188ms RepoModel.GetById 1ms app.codeStats 2ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/js/jsstr.cpp

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
C++ | 2220 lines | 1751 code | 236 blank | 233 comment | 405 complexity | c3cf6b74a7ab61e8451b13f010618873 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
   2 * vim: set ts=8 sw=4 et tw=99:
   3 *
   4 * ***** BEGIN LICENSE BLOCK *****
   5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
   6 *
   7 * The contents of this file are subject to the Mozilla Public License Version
   8 * 1.1 (the "License"); you may not use this file except in compliance with
   9 * the License. You may obtain a copy of the License at
  10 * http://www.mozilla.org/MPL/
  11 *
  12 * Software distributed under the License is distributed on an "AS IS" basis,
  13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14 * for the specific language governing rights and limitations under the
  15 * License.
  16 *
  17 * The Original Code is Mozilla Communicator client code, released
  18 * March 31, 1998.
  19 *
  20 * The Initial Developer of the Original Code is
  21 * Netscape Communications Corporation.
  22 * Portions created by the Initial Developer are Copyright (C) 1998
  23 * the Initial Developer. All Rights Reserved.
  24 *
  25 * Contributor(s):
  26 *
  27 * Alternatively, the contents of this file may be used under the terms of
  28 * either of the GNU General Public License Version 2 or later (the "GPL"),
  29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30 * in which case the provisions of the GPL or the LGPL are applicable instead
  31 * of those above. If you wish to allow use of your version of this file only
  32 * under the terms of either the GPL or the LGPL, and not to allow others to
  33 * use your version of this file under the terms of the MPL, indicate your
  34 * decision by deleting the provisions above and replace them with the notice
  35 * and other provisions required by the GPL or the LGPL. If you do not delete
  36 * the provisions above, a recipient may use your version of this file under
  37 * the terms of any one of the MPL, the GPL or the LGPL.
  38 *
  39 * ***** END LICENSE BLOCK ***** */
  40
  41/*
  42 * JS string type implementation.
  43 *
  44 * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these
  45 * native methods store strings (possibly newborn) converted from their 'this'
  46 * parameter and arguments on the stack: 'this' conversions at argv[-1], arg
  47 * conversions at their index (argv[0], argv[1]).  This is a legitimate method
  48 * of rooting things that might lose their newborn root due to subsequent GC
  49 * allocations in the same native method.
  50 */
  51#include "jsstddef.h"
  52#include <stdlib.h>
  53#include <string.h>
  54#include "jstypes.h"
  55#include "jsutil.h" /* Added by JSIFY */
  56#include "jshash.h" /* Added by JSIFY */
  57#include "jsprf.h"
  58#include "jsapi.h"
  59#include "jsarray.h"
  60#include "jsatom.h"
  61#include "jsbool.h"
  62#include "jsbuiltins.h"
  63#include "jscntxt.h"
  64#include "jsversion.h"
  65#include "jsgc.h"
  66#include "jsinterp.h"
  67#include "jslock.h"
  68#include "jsnum.h"
  69#include "jsobj.h"
  70#include "jsopcode.h"
  71#include "jsregexp.h"
  72#include "jsscope.h"
  73#include "jsstr.h"
  74#include "jsbit.h"
  75
  76#define JSSTRDEP_RECURSION_LIMIT        100
  77
  78size_t
  79js_MinimizeDependentStrings(JSString *str, int level, JSString **basep)
  80{
  81    JSString *base;
  82    size_t start, length;
  83
  84    JS_ASSERT(JSSTRING_IS_DEPENDENT(str));
  85    base = JSSTRDEP_BASE(str);
  86    start = JSSTRDEP_START(str);
  87    if (JSSTRING_IS_DEPENDENT(base)) {
  88        if (level < JSSTRDEP_RECURSION_LIMIT) {
  89            start += js_MinimizeDependentStrings(base, level + 1, &base);
  90        } else {
  91            do {
  92                start += JSSTRDEP_START(base);
  93                base = JSSTRDEP_BASE(base);
  94            } while (JSSTRING_IS_DEPENDENT(base));
  95        }
  96        if (start == 0) {
  97            JS_ASSERT(JSSTRDEP_IS_PREFIX(str));
  98            JSPREFIX_SET_BASE(str, base);
  99        } else if (start <= JSSTRDEP_START_MASK) {
 100            length = JSSTRDEP_LENGTH(str);
 101            JSSTRDEP_INIT(str, base, start, length);
 102        }
 103    }
 104    *basep = base;
 105    return start;
 106}
 107
 108jschar *
 109js_GetDependentStringChars(JSString *str)
 110{
 111    size_t start;
 112    JSString *base;
 113
 114    start = js_MinimizeDependentStrings(str, 0, &base);
 115    JS_ASSERT(start < JSFLATSTR_LENGTH(base));
 116    return JSFLATSTR_CHARS(base) + start;
 117}
 118
 119const jschar *
 120js_GetStringChars(JSContext *cx, JSString *str)
 121{
 122    if (!js_MakeStringImmutable(cx, str))
 123        return NULL;
 124    return JSFLATSTR_CHARS(str);
 125}
 126
 127JSString * JS_FASTCALL
 128js_ConcatStrings(JSContext *cx, JSString *left, JSString *right)
 129{
 130    size_t rn, ln, lrdist, n;
 131    jschar *rs, *ls, *s;
 132    JSString *ldep;             /* non-null if left should become dependent */
 133    JSString *str;
 134
 135    JSSTRING_CHARS_AND_LENGTH(right, rs, rn);
 136    if (rn == 0)
 137        return left;
 138
 139    JSSTRING_CHARS_AND_LENGTH(left, ls, ln);
 140    if (ln == 0)
 141        return right;
 142
 143    if (!JSSTRING_IS_MUTABLE(left)) {
 144        /* We must copy if left does not own a buffer to realloc. */
 145        s = (jschar *) JS_malloc(cx, (ln + rn + 1) * sizeof(jschar));
 146        if (!s)
 147            return NULL;
 148        js_strncpy(s, ls, ln);
 149        ldep = NULL;
 150    } else {
 151        /* We can realloc left's space and make it depend on our result. */
 152        JS_ASSERT(JSSTRING_IS_FLAT(left));
 153        s = (jschar *) JS_realloc(cx, ls, (ln + rn + 1) * sizeof(jschar));
 154        if (!s)
 155            return NULL;
 156
 157        /* Take care: right could depend on left! */
 158        lrdist = (size_t)(rs - ls);
 159        if (lrdist < ln)
 160            rs = s + lrdist;
 161        left->u.chars = ls = s;
 162        ldep = left;
 163    }
 164
 165    js_strncpy(s + ln, rs, rn);
 166    n = ln + rn;
 167    s[n] = 0;
 168    str = js_NewString(cx, s, n);
 169    if (!str) {
 170        /* Out of memory: clean up any space we (re-)allocated. */
 171        if (!ldep) {
 172            JS_free(cx, s);
 173        } else {
 174            s = (jschar *) JS_realloc(cx, ls, (ln + 1) * sizeof(jschar));
 175            if (s)
 176                left->u.chars = s;
 177        }
 178    } else {
 179        JSFLATSTR_SET_MUTABLE(str);
 180
 181        /* Morph left into a dependent prefix if we realloc'd its buffer. */
 182        if (ldep) {
 183            JSPREFIX_INIT(ldep, str, ln);
 184#ifdef DEBUG
 185          {
 186            JSRuntime *rt = cx->runtime;
 187            JS_RUNTIME_METER(rt, liveDependentStrings);
 188            JS_RUNTIME_METER(rt, totalDependentStrings);
 189            JS_LOCK_RUNTIME_VOID(rt,
 190                (rt->strdepLengthSum += (double)ln,
 191                 rt->strdepLengthSquaredSum += (double)ln * (double)ln));
 192          }
 193#endif
 194        }
 195    }
 196
 197    return str;
 198}
 199
 200const jschar *
 201js_UndependString(JSContext *cx, JSString *str)
 202{
 203    size_t n, size;
 204    jschar *s;
 205
 206    if (JSSTRING_IS_DEPENDENT(str)) {
 207        n = JSSTRDEP_LENGTH(str);
 208        size = (n + 1) * sizeof(jschar);
 209        s = (jschar *) JS_malloc(cx, size);
 210        if (!s)
 211            return NULL;
 212
 213        js_strncpy(s, JSSTRDEP_CHARS(str), n);
 214        s[n] = 0;
 215        JSFLATSTR_INIT(str, s, n);
 216
 217#ifdef DEBUG
 218        {
 219            JSRuntime *rt = cx->runtime;
 220            JS_RUNTIME_UNMETER(rt, liveDependentStrings);
 221            JS_RUNTIME_UNMETER(rt, totalDependentStrings);
 222            JS_LOCK_RUNTIME_VOID(rt,
 223                (rt->strdepLengthSum -= (double)n,
 224                 rt->strdepLengthSquaredSum -= (double)n * (double)n));
 225        }
 226#endif
 227    }
 228
 229    return JSFLATSTR_CHARS(str);
 230}
 231
 232JSBool
 233js_MakeStringImmutable(JSContext *cx, JSString *str)
 234{
 235    if (JSSTRING_IS_DEPENDENT(str) && !js_UndependString(cx, str)) {
 236        JS_RUNTIME_METER(cx->runtime, badUndependStrings);
 237        return JS_FALSE;
 238    }
 239    JSFLATSTR_CLEAR_MUTABLE(str);
 240    return JS_TRUE;
 241}
 242
 243static JSString *
 244ArgToRootedString(JSContext *cx, uintN argc, jsval *vp, uintN arg)
 245{
 246    JSObject *obj;
 247    JSString *str;
 248
 249    if (arg >= argc)
 250        return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
 251    vp += 2 + arg;
 252
 253    if (JSVAL_IS_OBJECT(*vp)) {
 254        obj = JSVAL_TO_OBJECT(*vp);
 255        if (!obj)
 256            return ATOM_TO_STRING(cx->runtime->atomState.nullAtom);
 257        if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, vp))
 258            return NULL;
 259    }
 260    if (JSVAL_IS_STRING(*vp))
 261        return JSVAL_TO_STRING(*vp);
 262    if (JSVAL_IS_INT(*vp)) {
 263        str = js_NumberToString(cx, JSVAL_TO_INT(*vp));
 264    } else if (JSVAL_IS_DOUBLE(*vp)) {
 265        str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(*vp));
 266    } else if (JSVAL_IS_BOOLEAN(*vp)) {
 267        return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[
 268                                  JSVAL_TO_BOOLEAN(*vp)? 1 : 0]);
 269    } else {
 270        JS_ASSERT(JSVAL_IS_VOID(*vp));
 271        return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
 272    }
 273    if (str)
 274        *vp = STRING_TO_JSVAL(str);
 275    return str;
 276}
 277
 278/*
 279 * Forward declarations for URI encode/decode and helper routines
 280 */
 281static JSBool
 282str_decodeURI(JSContext *cx, uintN argc, jsval *vp);
 283
 284static JSBool
 285str_decodeURI_Component(JSContext *cx, uintN argc, jsval *vp);
 286
 287static JSBool
 288str_encodeURI(JSContext *cx, uintN argc, jsval *vp);
 289
 290static JSBool
 291str_encodeURI_Component(JSContext *cx, uintN argc, jsval *vp);
 292
 293static uint32
 294Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length);
 295
 296/*
 297 * Contributions from the String class to the set of methods defined for the
 298 * global object.  escape and unescape used to be defined in the Mocha library,
 299 * but as ECMA decided to spec them, they've been moved to the core engine
 300 * and made ECMA-compliant.  (Incomplete escapes are interpreted as literal
 301 * characters by unescape.)
 302 */
 303
 304/*
 305 * Stuff to emulate the old libmocha escape, which took a second argument
 306 * giving the type of escape to perform.  Retained for compatibility, and
 307 * copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes.
 308 */
 309
 310#define URL_XALPHAS     ((uint8) 1)
 311#define URL_XPALPHAS    ((uint8) 2)
 312#define URL_PATH        ((uint8) 4)
 313
 314static const uint8 urlCharType[256] =
 315/*      Bit 0           xalpha          -- the alphas
 316 *      Bit 1           xpalpha         -- as xalpha but
 317 *                             converts spaces to plus and plus to %20
 318 *      Bit 2 ...       path            -- as xalphas but doesn't escape '/'
 319 */
 320    /*   0 1 2 3 4 5 6 7 8 9 A B C D E F */
 321    {    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,       /* 0x */
 322         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,       /* 1x */
 323         0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4,       /* 2x   !"#$%&'()*+,-./  */
 324         7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,       /* 3x  0123456789:;<=>?  */
 325         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,       /* 4x  @ABCDEFGHIJKLMNO  */
 326         7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7,       /* 5X  PQRSTUVWXYZ[\]^_  */
 327         0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,       /* 6x  `abcdefghijklmno  */
 328         7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,       /* 7X  pqrstuvwxyz{\}~  DEL */
 329         0, };
 330
 331/* This matches the ECMA escape set when mask is 7 (default.) */
 332
 333#define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask))
 334
 335/* See ECMA-262 Edition 3 B.2.1 */
 336JSBool
 337js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 338{
 339    JSString *str;
 340    size_t i, ni, length, newlength;
 341    const jschar *chars;
 342    jschar *newchars;
 343    jschar ch;
 344    jsint mask;
 345    jsdouble d;
 346    const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
 347                           '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
 348
 349    mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
 350    if (argc > 1) {
 351        d = js_ValueToNumber(cx, &argv[1]);
 352        if (JSVAL_IS_NULL(argv[1]))
 353            return JS_FALSE;
 354        if (!JSDOUBLE_IS_FINITE(d) ||
 355            (mask = (jsint)d) != d ||
 356            mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))
 357        {
 358            char numBuf[12];
 359            JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) mask);
 360            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
 361                                 JSMSG_BAD_STRING_MASK, numBuf);
 362            return JS_FALSE;
 363        }
 364    }
 365
 366    str = ArgToRootedString(cx, argc, argv - 2, 0);
 367    if (!str)
 368        return JS_FALSE;
 369
 370    JSSTRING_CHARS_AND_LENGTH(str, chars, length);
 371    newlength = length;
 372
 373    /* Take a first pass and see how big the result string will need to be. */
 374    for (i = 0; i < length; i++) {
 375        if ((ch = chars[i]) < 128 && IS_OK(ch, mask))
 376            continue;
 377        if (ch < 256) {
 378            if (mask == URL_XPALPHAS && ch == ' ')
 379                continue;   /* The character will be encoded as '+' */
 380            newlength += 2; /* The character will be encoded as %XX */
 381        } else {
 382            newlength += 5; /* The character will be encoded as %uXXXX */
 383        }
 384
 385        /*
 386         * This overflow test works because newlength is incremented by at
 387         * most 5 on each iteration.
 388         */
 389        if (newlength < length) {
 390            js_ReportAllocationOverflow(cx);
 391            return JS_FALSE;
 392        }
 393    }
 394
 395    if (newlength >= ~(size_t)0 / sizeof(jschar)) {
 396        js_ReportAllocationOverflow(cx);
 397        return JS_FALSE;
 398    }
 399
 400    newchars = (jschar *) JS_malloc(cx, (newlength + 1) * sizeof(jschar));
 401    if (!newchars)
 402        return JS_FALSE;
 403    for (i = 0, ni = 0; i < length; i++) {
 404        if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) {
 405            newchars[ni++] = ch;
 406        } else if (ch < 256) {
 407            if (mask == URL_XPALPHAS && ch == ' ') {
 408                newchars[ni++] = '+'; /* convert spaces to pluses */
 409            } else {
 410                newchars[ni++] = '%';
 411                newchars[ni++] = digits[ch >> 4];
 412                newchars[ni++] = digits[ch & 0xF];
 413            }
 414        } else {
 415            newchars[ni++] = '%';
 416            newchars[ni++] = 'u';
 417            newchars[ni++] = digits[ch >> 12];
 418            newchars[ni++] = digits[(ch & 0xF00) >> 8];
 419            newchars[ni++] = digits[(ch & 0xF0) >> 4];
 420            newchars[ni++] = digits[ch & 0xF];
 421        }
 422    }
 423    JS_ASSERT(ni == newlength);
 424    newchars[newlength] = 0;
 425
 426    str = js_NewString(cx, newchars, newlength);
 427    if (!str) {
 428        JS_free(cx, newchars);
 429        return JS_FALSE;
 430    }
 431    *rval = STRING_TO_JSVAL(str);
 432    return JS_TRUE;
 433}
 434#undef IS_OK
 435
 436static JSBool
 437str_escape(JSContext *cx, uintN argc, jsval *vp)
 438{
 439    JSObject *obj;
 440
 441    obj = JS_THIS_OBJECT(cx, vp);
 442    return obj && js_str_escape(cx, obj, argc, vp + 2, vp);
 443}
 444
 445/* See ECMA-262 Edition 3 B.2.2 */
 446static JSBool
 447str_unescape(JSContext *cx, uintN argc, jsval *vp)
 448{
 449    JSString *str;
 450    size_t i, ni, length;
 451    const jschar *chars;
 452    jschar *newchars;
 453    jschar ch;
 454
 455    str = ArgToRootedString(cx, argc, vp, 0);
 456    if (!str)
 457        return JS_FALSE;
 458
 459    JSSTRING_CHARS_AND_LENGTH(str, chars, length);
 460
 461    /* Don't bother allocating less space for the new string. */
 462    newchars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
 463    if (!newchars)
 464        return JS_FALSE;
 465    ni = i = 0;
 466    while (i < length) {
 467        ch = chars[i++];
 468        if (ch == '%') {
 469            if (i + 1 < length &&
 470                JS7_ISHEX(chars[i]) && JS7_ISHEX(chars[i + 1]))
 471            {
 472                ch = JS7_UNHEX(chars[i]) * 16 + JS7_UNHEX(chars[i + 1]);
 473                i += 2;
 474            } else if (i + 4 < length && chars[i] == 'u' &&
 475                       JS7_ISHEX(chars[i + 1]) && JS7_ISHEX(chars[i + 2]) &&
 476                       JS7_ISHEX(chars[i + 3]) && JS7_ISHEX(chars[i + 4]))
 477            {
 478                ch = (((((JS7_UNHEX(chars[i + 1]) << 4)
 479                        + JS7_UNHEX(chars[i + 2])) << 4)
 480                      + JS7_UNHEX(chars[i + 3])) << 4)
 481                    + JS7_UNHEX(chars[i + 4]);
 482                i += 5;
 483            }
 484        }
 485        newchars[ni++] = ch;
 486    }
 487    newchars[ni] = 0;
 488
 489    str = js_NewString(cx, newchars, ni);
 490    if (!str) {
 491        JS_free(cx, newchars);
 492        return JS_FALSE;
 493    }
 494    *vp = STRING_TO_JSVAL(str);
 495    return JS_TRUE;
 496}
 497
 498#if JS_HAS_UNEVAL
 499static JSBool
 500str_uneval(JSContext *cx, uintN argc, jsval *vp)
 501{
 502    JSString *str;
 503
 504    str = js_ValueToSource(cx, argc != 0 ? vp[2] : JSVAL_VOID);
 505    if (!str)
 506        return JS_FALSE;
 507    *vp = STRING_TO_JSVAL(str);
 508    return JS_TRUE;
 509}
 510#endif
 511
 512const char js_escape_str[] = "escape";
 513const char js_unescape_str[] = "unescape";
 514#if JS_HAS_UNEVAL
 515const char js_uneval_str[] = "uneval";
 516#endif
 517const char js_decodeURI_str[] = "decodeURI";
 518const char js_encodeURI_str[] = "encodeURI";
 519const char js_decodeURIComponent_str[] = "decodeURIComponent";
 520const char js_encodeURIComponent_str[] = "encodeURIComponent";
 521
 522static JSFunctionSpec string_functions[] = {
 523    JS_FN(js_escape_str,             str_escape,                1,0),
 524    JS_FN(js_unescape_str,           str_unescape,              1,0),
 525#if JS_HAS_UNEVAL
 526    JS_FN(js_uneval_str,             str_uneval,                1,0),
 527#endif
 528    JS_FN(js_decodeURI_str,          str_decodeURI,             1,0),
 529    JS_FN(js_encodeURI_str,          str_encodeURI,             1,0),
 530    JS_FN(js_decodeURIComponent_str, str_decodeURI_Component,   1,0),
 531    JS_FN(js_encodeURIComponent_str, str_encodeURI_Component,   1,0),
 532
 533    JS_FS_END
 534};
 535
 536jschar      js_empty_ucstr[]  = {0};
 537JSSubString js_EmptySubString = {0, js_empty_ucstr};
 538
 539enum string_tinyid {
 540    STRING_LENGTH = -1
 541};
 542
 543static JSPropertySpec string_props[] = {
 544    {js_length_str,     STRING_LENGTH,
 545                        JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED, 0,0},
 546    {0,0,0,0,0}
 547};
 548
 549static JSBool
 550str_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 551{
 552    jsval v;
 553    JSString *str;
 554    jsint slot;
 555
 556    if (!JSVAL_IS_INT(id))
 557        return JS_TRUE;
 558
 559    slot = JSVAL_TO_INT(id);
 560    if (slot == STRING_LENGTH) {
 561        if (OBJ_GET_CLASS(cx, obj) == &js_StringClass) {
 562            /* Follow ECMA-262 by fetching intrinsic length of our string. */
 563            v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
 564            JS_ASSERT(JSVAL_IS_STRING(v));
 565            str = JSVAL_TO_STRING(v);
 566        } else {
 567            /* Preserve compatibility: convert obj to a string primitive. */
 568            str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
 569            if (!str)
 570                return JS_FALSE;
 571        }
 572
 573        *vp = INT_TO_JSVAL((jsint) JSSTRING_LENGTH(str));
 574    }
 575    return JS_TRUE;
 576}
 577
 578#define STRING_ELEMENT_ATTRS (JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT)
 579
 580static JSBool
 581str_enumerate(JSContext *cx, JSObject *obj)
 582{
 583    jsval v;
 584    JSString *str, *str1;
 585    size_t i, length;
 586
 587    v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
 588    JS_ASSERT(JSVAL_IS_STRING(v));
 589    str = JSVAL_TO_STRING(v);
 590
 591    length = JSSTRING_LENGTH(str);
 592    for (i = 0; i < length; i++) {
 593        str1 = js_NewDependentString(cx, str, i, 1);
 594        if (!str1)
 595            return JS_FALSE;
 596        if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSID(i),
 597                                 STRING_TO_JSVAL(str1), NULL, NULL,
 598                                 STRING_ELEMENT_ATTRS, NULL)) {
 599            return JS_FALSE;
 600        }
 601    }
 602    return JS_TRUE;
 603}
 604
 605static JSBool
 606str_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
 607            JSObject **objp)
 608{
 609    jsval v;
 610    JSString *str, *str1;
 611    jsint slot;
 612
 613    if (!JSVAL_IS_INT(id) || (flags & JSRESOLVE_ASSIGNING))
 614        return JS_TRUE;
 615
 616    v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
 617    JS_ASSERT(JSVAL_IS_STRING(v));
 618    str = JSVAL_TO_STRING(v);
 619
 620    slot = JSVAL_TO_INT(id);
 621    if ((size_t)slot < JSSTRING_LENGTH(str)) {
 622        str1 = js_GetUnitString(cx, str, (size_t)slot);
 623        if (!str1)
 624            return JS_FALSE;
 625        if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSID(slot),
 626                                 STRING_TO_JSVAL(str1), NULL, NULL,
 627                                 STRING_ELEMENT_ATTRS, NULL)) {
 628            return JS_FALSE;
 629        }
 630        *objp = obj;
 631    }
 632    return JS_TRUE;
 633}
 634
 635JSClass js_StringClass = {
 636    js_String_str,
 637    JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE |
 638    JSCLASS_HAS_CACHED_PROTO(JSProto_String),
 639    JS_PropertyStub,   JS_PropertyStub,   str_getProperty,   JS_PropertyStub,
 640    str_enumerate, (JSResolveOp)str_resolve, JS_ConvertStub, JS_FinalizeStub,
 641    JSCLASS_NO_OPTIONAL_MEMBERS
 642};
 643
 644#define NORMALIZE_THIS(cx,vp,str)                                             \
 645    JS_BEGIN_MACRO                                                            \
 646        if (JSVAL_IS_STRING(vp[1])) {                                         \
 647            str = JSVAL_TO_STRING(vp[1]);                                     \
 648        } else {                                                              \
 649            str = NormalizeThis(cx, vp);                                      \
 650            if (!str)                                                         \
 651                return JS_FALSE;                                              \
 652        }                                                                     \
 653    JS_END_MACRO
 654
 655static JSString *
 656NormalizeThis(JSContext *cx, jsval *vp)
 657{
 658    JSString *str;
 659
 660    if (JSVAL_IS_NULL(vp[1]) && JSVAL_IS_NULL(JS_THIS(cx, vp)))
 661        return NULL;
 662    str = js_ValueToString(cx, vp[1]);
 663    if (!str)
 664        return NULL;
 665    vp[1] = STRING_TO_JSVAL(str);
 666    return str;
 667}
 668
 669#if JS_HAS_TOSOURCE
 670
 671/*
 672 * String.prototype.quote is generic (as are most string methods), unlike
 673 * toSource, toString, and valueOf.
 674 */
 675static JSBool
 676str_quote(JSContext *cx, uintN argc, jsval *vp)
 677{
 678    JSString *str;
 679
 680    NORMALIZE_THIS(cx, vp, str);
 681    str = js_QuoteString(cx, str, '"');
 682    if (!str)
 683        return JS_FALSE;
 684    *vp = STRING_TO_JSVAL(str);
 685    return JS_TRUE;
 686}
 687
 688static JSBool
 689str_toSource(JSContext *cx, uintN argc, jsval *vp)
 690{
 691    jsval v;
 692    JSString *str;
 693    size_t i, j, k, n;
 694    char buf[16];
 695    jschar *s, *t;
 696
 697    if (!js_GetPrimitiveThis(cx, vp, &js_StringClass, &v))
 698        return JS_FALSE;
 699    JS_ASSERT(JSVAL_IS_STRING(v));
 700    str = js_QuoteString(cx, JSVAL_TO_STRING(v), '"');
 701    if (!str)
 702        return JS_FALSE;
 703    j = JS_snprintf(buf, sizeof buf, "(new %s(", js_StringClass.name);
 704    JSSTRING_CHARS_AND_LENGTH(str, s, k);
 705    n = j + k + 2;
 706    t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
 707    if (!t)
 708        return JS_FALSE;
 709    for (i = 0; i < j; i++)
 710        t[i] = buf[i];
 711    for (j = 0; j < k; i++, j++)
 712        t[i] = s[j];
 713    t[i++] = ')';
 714    t[i++] = ')';
 715    t[i] = 0;
 716    str = js_NewString(cx, t, n);
 717    if (!str) {
 718        JS_free(cx, t);
 719        return JS_FALSE;
 720    }
 721    *vp = STRING_TO_JSVAL(str);
 722    return JS_TRUE;
 723}
 724
 725#endif /* JS_HAS_TOSOURCE */
 726
 727static JSBool
 728str_toString(JSContext *cx, uintN argc, jsval *vp)
 729{
 730    return js_GetPrimitiveThis(cx, vp, &js_StringClass, vp);
 731}
 732
 733/*
 734 * Java-like string native methods.
 735 */
 736
 737static JSString *
 738SubstringTail(JSContext *cx, JSString *str, jsdouble length, jsdouble begin, jsdouble end)
 739{
 740    if (begin < 0)
 741        begin = 0;
 742    else if (begin > length)
 743        begin = length;
 744
 745    if (end < 0)
 746        end = 0;
 747    else if (end > length)
 748        end = length;
 749    if (end < begin) {
 750        /* ECMA emulates old JDK1.0 java.lang.String.substring. */
 751        jsdouble tmp = begin;
 752        begin = end;
 753        end = tmp;
 754    }
 755
 756    return js_NewDependentString(cx, str, (size_t)begin, (size_t)(end - begin));
 757}
 758
 759static JSBool
 760str_substring(JSContext *cx, uintN argc, jsval *vp)
 761{
 762    JSString *str;
 763    jsdouble d;
 764    jsdouble length, begin, end;
 765
 766    NORMALIZE_THIS(cx, vp, str);
 767    if (argc != 0) {
 768        d = js_ValueToNumber(cx, &vp[2]);
 769        if (JSVAL_IS_NULL(vp[2]))
 770            return JS_FALSE;
 771        length = JSSTRING_LENGTH(str);
 772        begin = js_DoubleToInteger(d);
 773        if (argc == 1) {
 774            end = length;
 775        } else {
 776            d = js_ValueToNumber(cx, &vp[3]);
 777            if (JSVAL_IS_NULL(vp[3]))
 778                return JS_FALSE;
 779            end = js_DoubleToInteger(d);
 780        }
 781
 782        str = SubstringTail(cx, str, length, begin, end);
 783        if (!str)
 784            return JS_FALSE;
 785    }
 786    *vp = STRING_TO_JSVAL(str);
 787    return JS_TRUE;
 788}
 789
 790#ifdef JS_TRACER
 791static JSString* FASTCALL
 792String_p_toString(JSContext* cx, JSObject* obj)
 793{
 794    if (!JS_InstanceOf(cx, obj, &js_StringClass, NULL))
 795        return NULL;
 796    jsval v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE);
 797    JS_ASSERT(JSVAL_IS_STRING(v));
 798    return JSVAL_TO_STRING(v);
 799}
 800
 801static JSString* FASTCALL
 802String_p_substring(JSContext* cx, JSString* str, int32 begin, int32 end)
 803{
 804    JS_ASSERT(JS_ON_TRACE(cx));
 805
 806    size_t length = JSSTRING_LENGTH(str);
 807    return SubstringTail(cx, str, length, begin, end);
 808}
 809
 810static JSString* FASTCALL
 811String_p_substring_1(JSContext* cx, JSString* str, int32 begin)
 812{
 813    JS_ASSERT(JS_ON_TRACE(cx));
 814
 815    size_t length = JSSTRING_LENGTH(str);
 816    return SubstringTail(cx, str, length, begin, length);
 817}
 818#endif
 819
 820JSString* JS_FASTCALL
 821js_toLowerCase(JSContext *cx, JSString *str)
 822{
 823    size_t i, n;
 824    jschar *s, *news;
 825
 826    JSSTRING_CHARS_AND_LENGTH(str, s, n);
 827    news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
 828    if (!news)
 829        return NULL;
 830    for (i = 0; i < n; i++)
 831        news[i] = JS_TOLOWER(s[i]);
 832    news[n] = 0;
 833    str = js_NewString(cx, news, n);
 834    if (!str) {
 835        JS_free(cx, news);
 836        return NULL;
 837    }
 838    return str;
 839}
 840
 841static JSBool
 842str_toLowerCase(JSContext *cx, uintN argc, jsval *vp)
 843{
 844    JSString *str;
 845
 846    NORMALIZE_THIS(cx, vp, str);
 847    str = js_toLowerCase(cx, str);
 848    if (!str)
 849        return JS_FALSE;
 850    *vp = STRING_TO_JSVAL(str);
 851    return JS_TRUE;
 852}
 853
 854static JSBool
 855str_toLocaleLowerCase(JSContext *cx, uintN argc, jsval *vp)
 856{
 857    JSString *str;
 858
 859    /*
 860     * Forcefully ignore the first (or any) argument and return toLowerCase(),
 861     * ECMA has reserved that argument, presumably for defining the locale.
 862     */
 863    if (cx->localeCallbacks && cx->localeCallbacks->localeToLowerCase) {
 864        NORMALIZE_THIS(cx, vp, str);
 865        return cx->localeCallbacks->localeToLowerCase(cx, str, vp);
 866    }
 867    return str_toLowerCase(cx, 0, vp);
 868}
 869
 870JSString* JS_FASTCALL
 871js_toUpperCase(JSContext *cx, JSString *str)
 872{
 873    size_t i, n;
 874    jschar *s, *news;
 875
 876    JSSTRING_CHARS_AND_LENGTH(str, s, n);
 877    news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar));
 878    if (!news)
 879        return NULL;
 880    for (i = 0; i < n; i++)
 881        news[i] = JS_TOUPPER(s[i]);
 882    news[n] = 0;
 883    str = js_NewString(cx, news, n);
 884    if (!str) {
 885        JS_free(cx, news);
 886        return NULL;
 887    }
 888    return str;
 889}
 890
 891static JSBool
 892str_toUpperCase(JSContext *cx, uintN argc, jsval *vp)
 893{
 894    JSString *str;
 895
 896    NORMALIZE_THIS(cx, vp, str);
 897    str = js_toUpperCase(cx, str);
 898    if (!str)
 899        return JS_FALSE;
 900    *vp = STRING_TO_JSVAL(str);
 901    return JS_TRUE;
 902}
 903
 904static JSBool
 905str_toLocaleUpperCase(JSContext *cx, uintN argc, jsval *vp)
 906{
 907    JSString *str;
 908
 909    /*
 910     * Forcefully ignore the first (or any) argument and return toUpperCase(),
 911     * ECMA has reserved that argument, presumably for defining the locale.
 912     */
 913    if (cx->localeCallbacks && cx->localeCallbacks->localeToUpperCase) {
 914        NORMALIZE_THIS(cx, vp, str);
 915        return cx->localeCallbacks->localeToUpperCase(cx, str, vp);
 916    }
 917    return str_toUpperCase(cx, 0, vp);
 918}
 919
 920static JSBool
 921str_localeCompare(JSContext *cx, uintN argc, jsval *vp)
 922{
 923    JSString *str, *thatStr;
 924
 925    NORMALIZE_THIS(cx, vp, str);
 926    if (argc == 0) {
 927        *vp = JSVAL_ZERO;
 928    } else {
 929        thatStr = js_ValueToString(cx, vp[2]);
 930        if (!thatStr)
 931            return JS_FALSE;
 932        if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) {
 933            vp[2] = STRING_TO_JSVAL(thatStr);
 934            return cx->localeCallbacks->localeCompare(cx, str, thatStr, vp);
 935        }
 936        *vp = INT_TO_JSVAL(js_CompareStrings(str, thatStr));
 937    }
 938    return JS_TRUE;
 939}
 940
 941static JSBool
 942str_charAt(JSContext *cx, uintN argc, jsval *vp)
 943{
 944    jsval t;
 945    JSString *str;
 946    jsint i;
 947    jsdouble d;
 948
 949    t = vp[1];
 950    if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_INT(vp[2])) {
 951        str = JSVAL_TO_STRING(t);
 952        i = JSVAL_TO_INT(vp[2]);
 953        if ((size_t)i >= JSSTRING_LENGTH(str))
 954            goto out_of_range;
 955    } else {
 956        str = NormalizeThis(cx, vp);
 957        if (!str)
 958            return JS_FALSE;
 959
 960        if (argc == 0) {
 961            d = 0.0;
 962        } else {
 963            d = js_ValueToNumber(cx, &vp[2]);
 964            if (JSVAL_IS_NULL(vp[2]))
 965                return JS_FALSE;
 966            d = js_DoubleToInteger(d);
 967        }
 968
 969        if (d < 0 || JSSTRING_LENGTH(str) <= d)
 970            goto out_of_range;
 971        i = (jsint) d;
 972    }
 973
 974    str = js_GetUnitString(cx, str, (size_t)i);
 975    if (!str)
 976        return JS_FALSE;
 977    *vp = STRING_TO_JSVAL(str);
 978    return JS_TRUE;
 979
 980out_of_range:
 981    *vp = JS_GetEmptyStringValue(cx);
 982    return JS_TRUE;
 983}
 984
 985static JSBool
 986str_charCodeAt(JSContext *cx, uintN argc, jsval *vp)
 987{
 988    jsval t;
 989    JSString *str;
 990    jsint i;
 991    jsdouble d;
 992
 993    t = vp[1];
 994    if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_INT(vp[2])) {
 995        str = JSVAL_TO_STRING(t);
 996        i = JSVAL_TO_INT(vp[2]);
 997        if ((size_t)i >= JSSTRING_LENGTH(str))
 998            goto out_of_range;
 999    } else {
1000        str = NormalizeThis(cx, vp);
1001        if (!str)
1002            return JS_FALSE;
1003
1004        if (argc == 0) {
1005            d = 0.0;
1006        } else {
1007            d = js_ValueToNumber(cx, &vp[2]);
1008            if (JSVAL_IS_NULL(vp[2]))
1009                return JS_FALSE;
1010            d = js_DoubleToInteger(d);
1011        }
1012
1013        if (d < 0 || JSSTRING_LENGTH(str) <= d)
1014            goto out_of_range;
1015        i = (jsint) d;
1016    }
1017
1018    *vp = INT_TO_JSVAL(JSSTRING_CHARS(str)[i]);
1019    return JS_TRUE;
1020
1021out_of_range:
1022    *vp = JS_GetNaNValue(cx);
1023    return JS_TRUE;
1024}
1025
1026#ifdef JS_TRACER
1027int32 FASTCALL
1028js_String_p_charCodeAt(JSString* str, int32 i)
1029{
1030    if (i < 0 || (int32)JSSTRING_LENGTH(str) <= i)
1031        return -1;
1032    return JSSTRING_CHARS(str)[i];
1033}
1034#endif
1035
1036jsint
1037js_BoyerMooreHorspool(const jschar *text, jsint textlen,
1038                      const jschar *pat, jsint patlen,
1039                      jsint start)
1040{
1041    jsint i, j, k, m;
1042    uint8 skip[BMH_CHARSET_SIZE];
1043    jschar c;
1044
1045    JS_ASSERT(0 < patlen && patlen <= BMH_PATLEN_MAX);
1046    for (i = 0; i < BMH_CHARSET_SIZE; i++)
1047        skip[i] = (uint8)patlen;
1048    m = patlen - 1;
1049    for (i = 0; i < m; i++) {
1050        c = pat[i];
1051        if (c >= BMH_CHARSET_SIZE)
1052            return BMH_BAD_PATTERN;
1053        skip[c] = (uint8)(m - i);
1054    }
1055    for (k = start + m;
1056         k < textlen;
1057         k += ((c = text[k]) >= BMH_CHARSET_SIZE) ? patlen : skip[c]) {
1058        for (i = k, j = m; ; i--, j--) {
1059            if (j < 0)
1060                return i + 1;
1061            if (text[i] != pat[j])
1062                break;
1063        }
1064    }
1065    return -1;
1066}
1067
1068static JSBool
1069str_indexOf(JSContext *cx, uintN argc, jsval *vp)
1070{
1071    jsval t;
1072    JSString *str, *str2;
1073    const jschar *text, *pat;
1074    jsint i, j, index, textlen, patlen;
1075    jsdouble d;
1076
1077    t = vp[1];
1078    if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_STRING(vp[2])) {
1079        str = JSVAL_TO_STRING(t);
1080        str2 = JSVAL_TO_STRING(vp[2]);
1081    } else {
1082        str = NormalizeThis(cx, vp);
1083        if (!str)
1084            return JS_FALSE;
1085
1086        str2 = ArgToRootedString(cx, argc, vp, 0);
1087        if (!str2)
1088            return JS_FALSE;
1089    }
1090
1091    text = JSSTRING_CHARS(str);
1092    textlen = (jsint) JSSTRING_LENGTH(str);
1093    pat = JSSTRING_CHARS(str2);
1094    patlen = (jsint) JSSTRING_LENGTH(str2);
1095
1096    if (argc > 1) {
1097        d = js_ValueToNumber(cx, &vp[3]);
1098        if (JSVAL_IS_NULL(vp[3]))
1099            return JS_FALSE;
1100        d = js_DoubleToInteger(d);
1101        if (d < 0)
1102            i = 0;
1103        else if (d > textlen)
1104            i = textlen;
1105        else
1106            i = (jsint)d;
1107    } else {
1108        i = 0;
1109    }
1110    if (patlen == 0) {
1111        *vp = INT_TO_JSVAL(i);
1112        return JS_TRUE;
1113    }
1114
1115    /* XXX tune the BMH threshold (512) */
1116    if (textlen - i >= 512 && (jsuint)(patlen - 2) <= BMH_PATLEN_MAX - 2) {
1117        index = js_BoyerMooreHorspool(text, textlen, pat, patlen, i);
1118        if (index != BMH_BAD_PATTERN)
1119            goto out;
1120    }
1121
1122    index = -1;
1123    j = 0;
1124    while (i + j < textlen) {
1125        if (text[i + j] == pat[j]) {
1126            if (++j == patlen) {
1127                index = i;
1128                break;
1129            }
1130        } else {
1131            i++;
1132            j = 0;
1133        }
1134    }
1135
1136out:
1137    *vp = INT_TO_JSVAL(index);
1138    return JS_TRUE;
1139}
1140
1141static JSBool
1142str_lastIndexOf(JSContext *cx, uintN argc, jsval *vp)
1143{
1144    JSString *str, *str2;
1145    const jschar *text, *pat;
1146    jsint i, j, textlen, patlen;
1147    jsdouble d;
1148
1149    NORMALIZE_THIS(cx, vp, str);
1150    text = JSSTRING_CHARS(str);
1151    textlen = (jsint) JSSTRING_LENGTH(str);
1152
1153    str2 = ArgToRootedString(cx, argc, vp, 0);
1154    if (!str2)
1155        return JS_FALSE;
1156    pat = JSSTRING_CHARS(str2);
1157    patlen = (jsint) JSSTRING_LENGTH(str2);
1158
1159    if (argc > 1) {
1160        d = js_ValueToNumber(cx, &vp[3]);
1161        if (JSVAL_IS_NULL(vp[3]))
1162            return JS_FALSE;
1163        if (JSDOUBLE_IS_NaN(d)) {
1164            i = textlen;
1165        } else {
1166            d = js_DoubleToInteger(d);
1167            if (d < 0)
1168                i = 0;
1169            else if (d > textlen)
1170                i = textlen;
1171            else
1172                i = (jsint)d;
1173        }
1174    } else {
1175        i = textlen;
1176    }
1177
1178    if (patlen == 0) {
1179        *vp = INT_TO_JSVAL(i);
1180        return JS_TRUE;
1181    }
1182
1183    j = 0;
1184    while (i >= 0) {
1185        /* Don't assume that text is NUL-terminated: it could be dependent. */
1186        if (i + j < textlen && text[i + j] == pat[j]) {
1187            if (++j == patlen)
1188                break;
1189        } else {
1190            i--;
1191            j = 0;
1192        }
1193    }
1194    *vp = INT_TO_JSVAL(i);
1195    return JS_TRUE;
1196}
1197
1198static JSBool
1199js_TrimString(JSContext *cx, jsval *vp, JSBool trimLeft, JSBool trimRight)
1200{
1201    JSString *str;
1202    const jschar *chars;
1203    size_t length, begin, end;
1204
1205    NORMALIZE_THIS(cx, vp, str);
1206    JSSTRING_CHARS_AND_LENGTH(str, chars, length);
1207    begin = 0;
1208    end = length;
1209
1210    if (trimLeft) {
1211        while (begin < length && JS_ISSPACE(chars[begin]))
1212            ++begin;
1213    }
1214
1215    if (trimRight) {
1216        while (end > begin && JS_ISSPACE(chars[end-1]))
1217            --end;
1218    }
1219
1220    str = js_NewDependentString(cx, str, begin, end - begin);
1221    if (!str)
1222        return JS_FALSE;
1223
1224    *vp = STRING_TO_JSVAL(str);
1225    return JS_TRUE;
1226}
1227
1228static JSBool
1229str_trim(JSContext *cx, uintN argc, jsval *vp)
1230{
1231    return js_TrimString(cx, vp, JS_TRUE, JS_TRUE);
1232}
1233
1234static JSBool
1235str_trimLeft(JSContext *cx, uintN argc, jsval *vp)
1236{
1237    return js_TrimString(cx, vp, JS_TRUE, JS_FALSE);
1238}
1239
1240static JSBool
1241str_trimRight(JSContext *cx, uintN argc, jsval *vp)
1242{
1243    return js_TrimString(cx, vp, JS_FALSE, JS_TRUE);
1244}
1245
1246/*
1247 * Perl-inspired string functions.
1248 */
1249typedef struct GlobData {
1250    jsbytecode  *pc;            /* in: program counter resulting in us matching */
1251    uintN       flags;          /* inout: mode and flag bits, see below */
1252    uintN       optarg;         /* in: index of optional flags argument */
1253    JSString    *str;           /* out: 'this' parameter object as string */
1254    JSRegExp    *regexp;        /* out: regexp parameter object private data */
1255} GlobData;
1256
1257/*
1258 * Mode and flag bit definitions for match_or_replace's GlobData.flags field.
1259 */
1260#define MODE_MATCH      0x00    /* in: return match array on success */
1261#define MODE_REPLACE    0x01    /* in: match and replace */
1262#define MODE_SEARCH     0x02    /* in: search only, return match index or -1 */
1263#define GET_MODE(f)     ((f) & 0x03)
1264#define FORCE_FLAT      0x04    /* in: force flat (non-regexp) string match */
1265#define KEEP_REGEXP     0x08    /* inout: keep GlobData.regexp alive for caller
1266                                          of match_or_replace; if set on input
1267                                          but clear on output, regexp ownership
1268                                          does not pass to caller */
1269#define GLOBAL_REGEXP   0x10    /* out: regexp had the 'g' flag */
1270
1271static JSBool
1272match_or_replace(JSContext *cx,
1273                 JSBool (*glob)(JSContext *cx, jsint count, GlobData *data),
1274                 void (*destroy)(JSContext *cx, GlobData *data),
1275                 GlobData *data, uintN argc, jsval *vp)
1276{
1277    JSString *str, *src, *opt;
1278    JSObject *reobj;
1279    JSRegExp *re;
1280    size_t index, length;
1281    JSBool ok, test;
1282    jsint count;
1283
1284    NORMALIZE_THIS(cx, vp, str);
1285    data->str = str;
1286
1287    if (argc != 0 && VALUE_IS_REGEXP(cx, vp[2])) {
1288        reobj = JSVAL_TO_OBJECT(vp[2]);
1289        re = (JSRegExp *) JS_GetPrivate(cx, reobj);
1290    } else {
1291        src = ArgToRootedString(cx, argc, vp, 0);
1292        if (!src)
1293            return JS_FALSE;
1294        if (data->optarg < argc) {
1295            opt = js_ValueToString(cx, vp[2 + data->optarg]);
1296            if (!opt)
1297                return JS_FALSE;
1298        } else {
1299            opt = NULL;
1300        }
1301        re = js_NewRegExpOpt(cx, src, opt, (data->flags & FORCE_FLAT) != 0);
1302        if (!re)
1303            return JS_FALSE;
1304        reobj = NULL;
1305    }
1306    /* From here on, all control flow must reach the matching DROP. */
1307    data->regexp = re;
1308    HOLD_REGEXP(cx, re);
1309
1310    if (re->flags & JSREG_GLOB)
1311        data->flags |= GLOBAL_REGEXP;
1312    index = 0;
1313    if (GET_MODE(data->flags) == MODE_SEARCH) {
1314        ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, vp);
1315        if (ok) {
1316            *vp = (*vp == JSVAL_TRUE)
1317                  ? INT_TO_JSVAL(cx->regExpStatics.leftContext.length)
1318                  : INT_TO_JSVAL(-1);
1319        }
1320    } else if (data->flags & GLOBAL_REGEXP) {
1321        if (reobj) {
1322            /* Set the lastIndex property's reserved slot to 0. */
1323            ok = js_SetLastIndex(cx, reobj, 0);
1324        } else {
1325            ok = JS_TRUE;
1326        }
1327        if (ok) {
1328            length = JSSTRING_LENGTH(str);
1329            for (count = 0; index <= length; count++) {
1330                ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, vp);
1331                if (!ok || *vp != JSVAL_TRUE)
1332                    break;
1333                ok = glob(cx, count, data);
1334                if (!ok)
1335                    break;
1336                if (cx->regExpStatics.lastMatch.length == 0) {
1337                    if (index == length)
1338                        break;
1339                    index++;
1340                }
1341            }
1342            if (!ok && destroy)
1343                destroy(cx, data);
1344        }
1345    } else {
1346        if (GET_MODE(data->flags) == MODE_REPLACE) {
1347            test = JS_TRUE;
1348        } else {
1349            /*
1350             * MODE_MATCH implies str_match is being called from a script or a
1351             * scripted function.  If the caller cares only about testing null
1352             * vs. non-null return value, optimize away the array object that
1353             * would normally be returned in *vp.
1354             *
1355             * Assume a full array result is required, then prove otherwise.
1356             */
1357            test = JS_FALSE;
1358            if (data->pc && (*data->pc == JSOP_CALL || *data->pc == JSOP_NEW)) {
1359                JS_ASSERT(js_CodeSpec[*data->pc].length == 3);
1360                switch (data->pc[3]) {
1361                  case JSOP_POP:
1362                  case JSOP_IFEQ:
1363                  case JSOP_IFNE:
1364                  case JSOP_IFEQX:
1365                  case JSOP_IFNEX:
1366                    test = JS_TRUE;
1367                    break;
1368                  default:;
1369                }
1370            }
1371        }
1372        ok = js_ExecuteRegExp(cx, re, str, &index, test, vp);
1373    }
1374
1375    DROP_REGEXP(cx, re);
1376    if (reobj) {
1377        /* Tell our caller that it doesn't need to destroy data->regexp. */
1378        data->flags &= ~KEEP_REGEXP;
1379    } else if (!ok || !(data->flags & KEEP_REGEXP)) {
1380        /* Caller didn't want to keep data->regexp, so null and destroy it.  */
1381        data->regexp = NULL;
1382        js_DestroyRegExp(cx, re);
1383    }
1384
1385    return ok;
1386}
1387
1388typedef struct MatchData {
1389    GlobData    base;
1390    jsval       *arrayval;      /* NB: local root pointer */
1391} MatchData;
1392
1393static JSBool
1394match_glob(JSContext *cx, jsint count, GlobData *data)
1395{
1396    MatchData *mdata;
1397    JSObject *arrayobj;
1398    JSSubString *matchsub;
1399    JSString *matchstr;
1400    jsval v;
1401
1402    mdata = (MatchData *)data;
1403    arrayobj = JSVAL_TO_OBJECT(*mdata->arrayval);
1404    if (!arrayobj) {
1405        arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL);
1406        if (!arrayobj)
1407            return JS_FALSE;
1408        *mdata->arrayval = OBJECT_TO_JSVAL(arrayobj);
1409    }
1410    matchsub = &cx->regExpStatics.lastMatch;
1411    matchstr = js_NewStringCopyN(cx, matchsub->chars, matchsub->length);
1412    if (!matchstr)
1413        return JS_FALSE;
1414    v = STRING_TO_JSVAL(matchstr);
1415    JS_ASSERT(count <= JSVAL_INT_MAX);
1416
1417    JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING);
1418    return OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(count), &v);
1419}
1420
1421JSBool
1422js_StringMatchHelper(JSContext *cx, uintN argc, jsval *vp, jsbytecode *pc)
1423{
1424    JSTempValueRooter tvr;
1425    MatchData mdata;
1426    JSBool ok;
1427
1428    JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
1429    mdata.base.pc = pc;
1430    mdata.base.flags = MODE_MATCH;
1431    mdata.base.optarg = 1;
1432    mdata.arrayval = &tvr.u.value;
1433    ok = match_or_replace(cx, match_glob, NULL, &mdata.base, argc, vp);
1434    if (ok && !JSVAL_IS_NULL(*mdata.arrayval))
1435        *vp = *mdata.arrayval;
1436    JS_POP_TEMP_ROOT(cx, &tvr);
1437    return ok;
1438}
1439
1440static JSBool
1441str_match(JSContext *cx, uintN argc, jsval *vp)
1442{
1443    JSStackFrame *fp;
1444
1445    for (fp = cx->fp; fp && !fp->regs; fp = fp->down)
1446        JS_ASSERT(!fp->script);
1447    return js_StringMatchHelper(cx, argc, vp, fp ? fp->regs->pc : NULL);
1448}
1449
1450#ifdef JS_TRACER
1451static JSObject* FASTCALL
1452String_p_match(JSContext* cx, JSString* str, jsbytecode *pc, JSObject* regexp)
1453{
1454    jsval vp[3] = { JSVAL_NULL, STRING_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp) };
1455    if (!js_StringMatchHelper(cx, 1, vp, pc))
1456        return (JSObject*) JSVAL_TO_BOOLEAN(JSVAL_VOID);
1457    JS_ASSERT(JSVAL_IS_NULL(vp[0]) ||
1458              (!JSVAL_IS_PRIMITIVE(vp[0]) && OBJ_IS_ARRAY(cx, JSVAL_TO_OBJECT(vp[0]))));
1459    return JSVAL_TO_OBJECT(vp[0]);
1460}
1461
1462static JSObject* FASTCALL
1463String_p_match_obj(JSContext* cx, JSObject* str, jsbytecode *pc, JSObject* regexp)
1464{
1465    jsval vp[3] = { JSVAL_NULL, OBJECT_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp) };
1466    if (!js_StringMatchHelper(cx, 1, vp, pc))
1467        return (JSObject*) JSVAL_TO_BOOLEAN(JSVAL_VOID);
1468    JS_ASSERT(JSVAL_IS_NULL(vp[0]) ||
1469              (!JSVAL_IS_PRIMITIVE(vp[0]) && OBJ_IS_ARRAY(cx, JSVAL_TO_OBJECT(vp[0]))));
1470    return JSVAL_TO_OBJECT(vp[0]);
1471}
1472#endif
1473
1474static JSBool
1475str_search(JSContext *cx, uintN argc, jsval *vp)
1476{
1477    GlobData data;
1478
1479    data.flags = MODE_SEARCH;
1480    data.optarg = 1;
1481    return match_or_replace(cx, NULL, NULL, &data, argc, vp);
1482}
1483
1484typedef struct ReplaceData {
1485    GlobData    base;           /* base struct state */
1486    JSObject    *lambda;        /* replacement function object or null */
1487    JSString    *repstr;        /* replacement string */
1488    jschar      *dollar;        /* null or pointer to first $ in repstr */
1489    jschar      *dollarEnd;     /* limit pointer for js_strchr_limit */
1490    jschar      *chars;         /* result chars, null initially */
1491    size_t      length;         /* result length, 0 initially */
1492    jsint       index;          /* index in result of next replacement */
1493    jsint       leftIndex;      /* left context index in base.str->chars */
1494    JSSubString dollarStr;      /* for "$$" interpret_dollar result */
1495} ReplaceData;
1496
1497static JSSubString *
1498interpret_dollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData *rdata,
1499                 size_t *skip)
1500{
1501    JSRegExpStatics *res;
1502    jschar dc, *cp;
1503    uintN num, tmp;
1504
1505    JS_ASSERT(*dp == '$');
1506
1507    /* If there is only a dollar, bail now */
1508    if (dp + 1 >= ep)
1509        return NULL;
1510
1511    /* Interpret all Perl match-induced dollar variables. */
1512    res = &cx->regExpStatics;
1513    dc = dp[1];
1514    if (JS7_ISDEC(dc)) {
1515        /* ECMA-262 Edition 3: 1-9 or 01-99 */
1516        num = JS7_UNDEC(dc);
1517        if (num > res->parenCount)
1518            return NULL;
1519
1520        cp = dp + 2;
1521        if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
1522            tmp = 10 * num + JS7_UNDEC(dc);
1523            if (tmp <= res->parenCount) {
1524                cp++;
1525                num = tmp;
1526            }
1527        }
1528        if (num == 0)
1529            return NULL;
1530
1531        /* Adjust num from 1 $n-origin to 0 array-index-origin. */
1532        num--;
1533        *skip = cp - dp;
1534        return REGEXP_PAREN_SUBSTRING(res, num);
1535    }
1536
1537    *skip = 2;
1538    switch (dc) {
1539      case '$':
1540        rdata->dollarStr.chars = dp;
1541        rdata->dollarStr.length = 1;
1542        return &rdata->dollarStr;
1543      case '&':
1544        return &res->lastMatch;
1545      case '+':
1546        return &res->lastParen;
1547      case '`':
1548        return &res->leftContext;
1549      case '\'':
1550        return &res->rightContext;
1551    }
1552    return NULL;
1553}
1554
1555static JSBool
1556find_replen(JSContext *cx, ReplaceData *rdata, size_t *sizep)
1557{
1558    JSString *repstr;
1559    size_t replen, skip;
1560    jschar *dp, *ep;
1561    JSSubString *sub;
1562    JSObject *lambda;
1563
1564    lambda = rdata->lambda;
1565    if (lambda) {
1566        uintN argc, i, j, m, n, p;
1567        jsval *invokevp, *sp;
1568        void *mark;
1569        JSBool ok;
1570
1571        /*
1572         * Save the regExpStatics from the current regexp, since they may be
1573         * clobbered by a RegExp usage in the lambda function.  Note that all
1574         * members of JSRegExpStatics are JSSubStrings, so not GC roots, save
1575         * input, which is rooted otherwise via vp[1] in str_replace.
1576         */
1577        JSRegExpStatics save = cx->regExpStatics;
1578        JSBool freeMoreParens = JS_FALSE;
1579
1580        /*
1581         * In the lambda case, not only do we find the replacement string's
1582         * length, we compute repstr and return it via rdata for use within
1583         * do_replace.  The lambda is called with arguments ($&, $1, $2, ...,
1584         * index, input), i.e., all the properties of a regexp match array.
1585         * For $&, etc., we must create string jsvals from cx->regExpStatics.
1586         * We grab up stack space to keep the newborn strings GC-rooted.
1587         */
1588        p = rdata->base.regexp->parenCount;
1589        argc = 1 + p + 2;
1590        invokevp = js_AllocStack(cx, 2 + argc, &mark);
1591        if (!invokevp)
1592            return JS_FALSE;
1593
1594        /* Push lambda and its 'this' parameter. */
1595        sp = invokevp;
1596        *sp++ = OBJECT_TO_JSVAL(lambda);
1597        *sp++ = OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, lambda));
1598
1599#define PUSH_REGEXP_STATIC(sub)                                               \
1600    JS_BEGIN_MACRO                                                            \
1601        JSString *str = js_NewStringCopyN(cx,                                 \
1602                                          cx->regExpStatics.sub.chars,        \
1603                                          cx->regExpStatics.sub.length);      \
1604        if (!str) {                                                           \
1605            ok = JS_FALSE;                                                    \
1606            goto lambda_out;                                                  \
1607        }                                                                     \
1608        *sp++ = STRING_TO_JSVAL(str);                                         \
1609    JS_END_MACRO
1610
1611        /* Push $&, $1, $2, ... */
1612        PUSH_REGEXP_STATIC(lastMatch);
1613        i = 0;
1614        m = cx->regExpStatics.parenCount;
1615        n = JS_MIN(m, 9);
1616        for (j = 0; i < n; i++, j++)
1617            PUSH_REGEXP_STATIC(parens[j]);
1618        for (j = 0; i < m; i++, j++)
1619            PUSH_REGEXP_STATIC(moreParens[j]);
1620
1621        /*
1622         * We need to clear moreParens in the top-of-stack cx->regExpStatics
1623         * to it won't be possibly realloc'ed, leaving the bottom-of-stack
1624         * moreParens pointing to freed memory.
1625         */
1626        cx->regExpStatics.moreParens = NULL;
1627        freeMoreParens = JS_TRUE;
1628
1629#undef PUSH_REGEXP_STATIC
1630
1631        /* Make sure to push undefined for any unmatched parens. */
1632        for (; i < p; i++)
1633            *sp++ = JSVAL_VOID;
1634
1635        /* Push match index and input string. */
1636        *sp++ = INT_TO_JSVAL((jsint)cx->regExpStatics.leftContext.length);
1637        *sp++ = STRING_TO_JSVAL(rdata->base.str);
1638
1639        ok = js_Invoke(cx, argc, invokevp, 0);
1640        if (ok) {
1641            /*
1642             * NB: we count on the newborn string root to hold any string
1643             * created by this js_ValueToString that would otherwise be GC-
1644             * able, until we use rdata->repstr in do_replace.
1645             */
1646            repstr = js_ValueToString(cx, *invokevp);
1647            if (!repstr) {
1648                ok = JS_FALSE;
1649            } else {
1650                rdata->repstr = repstr;
1651                *sizep = JSSTRING_LENGTH(repstr);
1652            }
1653        }
1654
1655      lambda_out:
1656        js_FreeStack(cx, mark);
1657        if (freeMoreParens)
1658            JS_free(cx, cx->regExpStatics.moreParens);
1659        cx->regExpStatics = save;
1660        return ok;
1661    }
1662
1663    repstr = rdata->repstr;
1664    replen = JSSTRING_LENGTH(repstr);
1665    for (dp = rdata->dollar, ep = rdata->dollarEnd; dp;
1666         dp = js_strchr_limit(dp, '$', ep)) {
1667        sub = interpret_dollar(cx, dp, ep, rdata, &skip);
1668        if (sub) {
1669            replen += sub->length - skip;
1670            dp += skip;
1671        }
1672        else
1673            dp++;
1674    }
1675    *sizep = replen;
1676    return JS_TRUE;
1677}
1678
1679static void
1680do_replace(JSContext *cx, ReplaceData *rdata, jschar *chars)
1681{
1682    JSString *repstr;
1683    jschar *bp, *cp, *dp, *ep;
1684    size_t len, skip;
1685    JSSubString *sub;
1686
1687    repstr = rdata->repstr;
1688    bp = cp = JSSTRING_CHARS(repstr);
1689    for (dp = rdata->dollar, ep = rdata->dollarEnd; dp;
1690         dp = js_strchr_limit(dp, '$', ep)) {
1691        len = dp - cp;
1692        js_strncpy(chars, cp, len);
1693        chars += len;
1694        cp = dp;
1695        sub = interpret_dollar(cx, dp, ep, rdata, &skip);
1696        if (sub) {
1697            len = sub->length;
1698            js_strncpy(chars, sub->chars, len);
1699            chars += len;
1700            cp += skip;
1701            dp += skip;
1702        } else {
1703            dp++;
1704        }
1705    }
1706    js_strncpy(chars, cp, JSSTRING_LENGTH(repstr) - (

Large files files are truncated, but you can click here to view the full file