StringPrototype.cpp 59.7 KB
Newer Older
1 2
/*
 *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3
 *  Copyright (C) 2004, 2005, 2006, 2007, 2008, 2013 Apple Inc. All rights reserved.
4
 *  Copyright (C) 2009 Torch Mobile, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
mjs's avatar
mjs committed
18
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
 *
20 21
 */

mjs's avatar
mjs committed
22
#include "config.h"
weinig@apple.com's avatar
weinig@apple.com committed
23
#include "StringPrototype.h"
mjs's avatar
mjs committed
24

25
#include "ButterflyInlines.h"
oliver@apple.com's avatar
oliver@apple.com committed
26
#include "CachedCall.h"
27
#include "CopiedSpaceInlines.h"
28
#include "Error.h"
29
#include "Executable.h"
30
#include "JSGlobalObjectFunctions.h"
31
#include "JSArray.h"
32
#include "JSFunction.h"
33
#include "JSStringBuilder.h"
34
#include "Lookup.h"
35
#include "ObjectPrototype.h"
36
#include "Operations.h"
37
#include "PropertyNameArray.h"
38
#include "RegExpCache.h"
39
#include "RegExpConstructor.h"
40
#include "RegExpMatchesArray.h"
41
#include "RegExpObject.h"
42
#include <wtf/ASCIICType.h>
43
#include <wtf/MathExtras.h>
ap@webkit.org's avatar
ap@webkit.org committed
44
#include <wtf/unicode/Collator.h>
andersca's avatar
andersca committed
45

darin's avatar
darin committed
46 47
using namespace WTF;

48
namespace JSC {
49

50
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(StringPrototype);
ggaren@apple.com's avatar
ggaren@apple.com committed
51

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
static EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncMatch(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSearch(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSplit(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncBig(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSmall(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncBlink(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncBold(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncFixed(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncItalics(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncStrike(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSub(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncSup(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncFontcolor(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncFontsize(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncAnchor(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncLink(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState*);
static EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState*);
84

85
const ClassInfo StringPrototype::s_info = { "String", &StringObject::s_info, 0, 0, CREATE_METHOD_TABLE(StringPrototype) };
weinig@apple.com's avatar
weinig@apple.com committed
86

87
// ECMA 15.5.4
88 89
StringPrototype::StringPrototype(VM& vm, Structure* structure)
    : StringObject(vm, structure)
90
{
91 92
}

93
void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSString* nameAndMessage)
94
{
ggaren@apple.com's avatar
ggaren@apple.com committed
95
    Base::finishCreation(vm, nameAndMessage);
96
    ASSERT(inherits(info()));
97

98 99
    JSC_NATIVE_INTRINSIC_FUNCTION(vm.propertyNames->toString, stringProtoFuncToString, DontEnum, 0, StringPrototypeValueOfIntrinsic);
    JSC_NATIVE_INTRINSIC_FUNCTION(vm.propertyNames->valueOf, stringProtoFuncToString, DontEnum, 0, StringPrototypeValueOfIntrinsic);
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    JSC_NATIVE_INTRINSIC_FUNCTION("charAt", stringProtoFuncCharAt, DontEnum, 1, CharAtIntrinsic);
    JSC_NATIVE_INTRINSIC_FUNCTION("charCodeAt", stringProtoFuncCharCodeAt, DontEnum, 1, CharCodeAtIntrinsic);
    JSC_NATIVE_FUNCTION("concat", stringProtoFuncConcat, DontEnum, 1);
    JSC_NATIVE_FUNCTION("indexOf", stringProtoFuncIndexOf, DontEnum, 1);
    JSC_NATIVE_FUNCTION("lastIndexOf", stringProtoFuncLastIndexOf, DontEnum, 1);
    JSC_NATIVE_FUNCTION("match", stringProtoFuncMatch, DontEnum, 1);
    JSC_NATIVE_FUNCTION("replace", stringProtoFuncReplace, DontEnum, 2);
    JSC_NATIVE_FUNCTION("search", stringProtoFuncSearch, DontEnum, 1);
    JSC_NATIVE_FUNCTION("slice", stringProtoFuncSlice, DontEnum, 2);
    JSC_NATIVE_FUNCTION("split", stringProtoFuncSplit, DontEnum, 2);
    JSC_NATIVE_FUNCTION("substr", stringProtoFuncSubstr, DontEnum, 2);
    JSC_NATIVE_FUNCTION("substring", stringProtoFuncSubstring, DontEnum, 2);
    JSC_NATIVE_FUNCTION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0);
    JSC_NATIVE_FUNCTION("toUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
    JSC_NATIVE_FUNCTION("localeCompare", stringProtoFuncLocaleCompare, DontEnum, 1);
    JSC_NATIVE_FUNCTION("toLocaleLowerCase", stringProtoFuncToLowerCase, DontEnum, 0);
    JSC_NATIVE_FUNCTION("toLocaleUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
    JSC_NATIVE_FUNCTION("big", stringProtoFuncBig, DontEnum, 0);
    JSC_NATIVE_FUNCTION("small", stringProtoFuncSmall, DontEnum, 0);
    JSC_NATIVE_FUNCTION("blink", stringProtoFuncBlink, DontEnum, 0);
    JSC_NATIVE_FUNCTION("bold", stringProtoFuncBold, DontEnum, 0);
    JSC_NATIVE_FUNCTION("fixed", stringProtoFuncFixed, DontEnum, 0);
    JSC_NATIVE_FUNCTION("italics", stringProtoFuncItalics, DontEnum, 0);
    JSC_NATIVE_FUNCTION("strike", stringProtoFuncStrike, DontEnum, 0);
    JSC_NATIVE_FUNCTION("sub", stringProtoFuncSub, DontEnum, 0);
    JSC_NATIVE_FUNCTION("sup", stringProtoFuncSup, DontEnum, 0);
    JSC_NATIVE_FUNCTION("fontcolor", stringProtoFuncFontcolor, DontEnum, 1);
    JSC_NATIVE_FUNCTION("fontsize", stringProtoFuncFontsize, DontEnum, 1);
    JSC_NATIVE_FUNCTION("anchor", stringProtoFuncAnchor, DontEnum, 1);
    JSC_NATIVE_FUNCTION("link", stringProtoFuncLink, DontEnum, 1);
    JSC_NATIVE_FUNCTION("trim", stringProtoFuncTrim, DontEnum, 0);
    JSC_NATIVE_FUNCTION("trimLeft", stringProtoFuncTrimLeft, DontEnum, 0);
    JSC_NATIVE_FUNCTION("trimRight", stringProtoFuncTrimRight, DontEnum, 0);
133

weinig@apple.com's avatar
weinig@apple.com committed
134
    // The constructor will be added later, after StringConstructor has been built
135
    putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
136 137
}

138
StringPrototype* StringPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
139
{
140 141 142
    JSString* empty = jsEmptyString(&vm);
    StringPrototype* prototype = new (NotNull, allocateCell<StringPrototype>(vm.heap)) StringPrototype(vm, structure);
    prototype->finishCreation(vm, globalObject, empty);
143
    return prototype;
144 145
}

weinig@apple.com's avatar
weinig@apple.com committed
146
// ------------------------------ Functions --------------------------
147

barraclough@apple.com's avatar
barraclough@apple.com committed
148 149 150
// Helper for producing a JSString for 'string', where 'string' was been produced by
// calling ToString on 'originalValue'. In cases where 'originalValue' already was a
// string primitive we can just use this, otherwise we need to allocate a new JSString.
151
static inline JSString* jsStringWithReuse(ExecState* exec, JSValue originalValue, const String& string)
barraclough@apple.com's avatar
barraclough@apple.com committed
152 153 154 155 156 157 158 159
{
    if (originalValue.isString()) {
        ASSERT(asString(originalValue)->value(exec) == string);
        return asString(originalValue);
    }
    return jsString(exec, string);
}

160
template <typename CharType>
161
static NEVER_INLINE String substituteBackreferencesSlow(const String& replacement, const String& source, const int* ovector, RegExp* reg, size_t i)
mjs's avatar
mjs committed
162
{
163
    Vector<CharType> substitutedReplacement;
weinig@apple.com's avatar
weinig@apple.com committed
164
    int offset = 0;
165
    do {
166
        if (i + 1 == replacement.length())
weinig@apple.com's avatar
weinig@apple.com committed
167 168
            break;

darin@apple.com's avatar
darin@apple.com committed
169
        UChar ref = replacement[i + 1];
weinig@apple.com's avatar
weinig@apple.com committed
170 171 172
        if (ref == '$') {
            // "$$" -> "$"
            ++i;
173
            substitutedReplacement.append(replacement.getCharactersWithUpconvert<CharType>() + offset, i - offset);
weinig@apple.com's avatar
weinig@apple.com committed
174
            offset = i + 1;
ggaren's avatar
ggaren committed
175
            continue;
ddkilzer's avatar
ddkilzer committed
176 177
        }

weinig@apple.com's avatar
weinig@apple.com committed
178 179 180 181 182 183 184 185 186 187 188
        int backrefStart;
        int backrefLength;
        int advance = 0;
        if (ref == '&') {
            backrefStart = ovector[0];
            backrefLength = ovector[1] - backrefStart;
        } else if (ref == '`') {
            backrefStart = 0;
            backrefLength = ovector[0];
        } else if (ref == '\'') {
            backrefStart = ovector[1];
189
            backrefLength = source.length() - backrefStart;
darin@apple.com's avatar
darin@apple.com committed
190
        } else if (reg && ref >= '0' && ref <= '9') {
weinig@apple.com's avatar
weinig@apple.com committed
191 192 193 194
            // 1- and 2-digit back references are allowed
            unsigned backrefIndex = ref - '0';
            if (backrefIndex > reg->numSubpatterns())
                continue;
195
            if (replacement.length() > i + 2) {
weinig@apple.com's avatar
weinig@apple.com committed
196 197 198 199 200 201 202 203 204
                ref = replacement[i + 2];
                if (ref >= '0' && ref <= '9') {
                    backrefIndex = 10 * backrefIndex + ref - '0';
                    if (backrefIndex > reg->numSubpatterns())
                        backrefIndex = backrefIndex / 10;   // Fall back to the 1-digit reference
                    else
                        advance = 1;
                }
            }
darin@apple.com's avatar
darin@apple.com committed
205 206
            if (!backrefIndex)
                continue;
weinig@apple.com's avatar
weinig@apple.com committed
207 208 209 210 211 212
            backrefStart = ovector[2 * backrefIndex];
            backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
        } else
            continue;

        if (i - offset)
213
            substitutedReplacement.append(replacement.getCharactersWithUpconvert<CharType>() + offset, i - offset);
weinig@apple.com's avatar
weinig@apple.com committed
214 215
        i += 1 + advance;
        offset = i + 1;
216
        if (backrefStart >= 0)
217
            substitutedReplacement.append(source.getCharactersWithUpconvert<CharType>() + backrefStart, backrefLength);
218
    } while ((i = replacement.find('$', i + 1)) != notFound);
219

220
    if (replacement.length() - offset)
221
        substitutedReplacement.append(replacement.getCharactersWithUpconvert<CharType>() + offset, replacement.length() - offset);
222

223
    substitutedReplacement.shrinkToFit();
224
    return String::adopt(substitutedReplacement);
225 226
}

227
static inline String substituteBackreferences(const String& replacement, const String& source, const int* ovector, RegExp* reg)
228
{
229
    size_t i = replacement.find('$');
230 231 232 233 234
    if (UNLIKELY(i != notFound)) {
        if (replacement.is8Bit() && source.is8Bit())
            return substituteBackreferencesSlow<LChar>(replacement, source, ovector, reg, i);
        return substituteBackreferencesSlow<UChar>(replacement, source, ovector, reg, i);
    }
235
    return replacement;
mjs's avatar
mjs committed
236
}
ap@webkit.org's avatar
ap@webkit.org committed
237

238
static inline int localeCompare(const String& a, const String& b)
andersca's avatar
andersca committed
239
{
240
    return Collator::userDefault()->collate(reinterpret_cast<const ::UChar*>(a.characters()), a.length(), reinterpret_cast<const ::UChar*>(b.characters()), b.length());
weinig's avatar
weinig committed
241
}
andersca's avatar
andersca committed
242

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
struct StringRange {
public:
    StringRange(int pos, int len)
        : position(pos)
        , length(len)
    {
    }

    StringRange()
    {
    }

    int position;
    int length;
};

259
static ALWAYS_INLINE JSValue jsSpliceSubstrings(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount)
260 261 262 263 264 265 266
{
    if (rangeCount == 1) {
        int sourceSize = source.length();
        int position = substringRanges[0].position;
        int length = substringRanges[0].length;
        if (position <= 0 && length >= sourceSize)
            return sourceVal;
267
        // We could call String::substringSharingImpl(), but this would result in redundant checks.
andersca@apple.com's avatar
andersca@apple.com committed
268
        return jsString(exec, StringImpl::create(source.impl(), std::max(0, position), std::min(sourceSize, length)));
269 270 271 272 273 274 275
    }

    int totalLength = 0;
    for (int i = 0; i < rangeCount; i++)
        totalLength += substringRanges[i].length;

    if (!totalLength)
276
        return jsEmptyString(exec);
277

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
    if (source.is8Bit()) {
        LChar* buffer;
        const LChar* sourceData = source.characters8();
        RefPtr<StringImpl> impl = StringImpl::tryCreateUninitialized(totalLength, buffer);
        if (!impl)
            return throwOutOfMemoryError(exec);

        int bufferPos = 0;
        for (int i = 0; i < rangeCount; i++) {
            if (int srcLen = substringRanges[i].length) {
                StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
                bufferPos += srcLen;
            }
        }

        return jsString(exec, impl.release());
    }

296
    UChar* buffer;
297 298
    const UChar* sourceData = source.characters16();

299 300 301 302 303 304 305
    RefPtr<StringImpl> impl = StringImpl::tryCreateUninitialized(totalLength, buffer);
    if (!impl)
        return throwOutOfMemoryError(exec);

    int bufferPos = 0;
    for (int i = 0; i < rangeCount; i++) {
        if (int srcLen = substringRanges[i].length) {
306
            StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
307 308 309 310 311 312 313
            bufferPos += srcLen;
        }
    }

    return jsString(exec, impl.release());
}

314
static ALWAYS_INLINE JSValue jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount, const String* separators, int separatorCount)
315 316
{
    if (rangeCount == 1 && separatorCount == 0) {
317
        int sourceSize = source.length();
318 319 320 321
        int position = substringRanges[0].position;
        int length = substringRanges[0].length;
        if (position <= 0 && length >= sourceSize)
            return sourceVal;
322
        // We could call String::substringSharingImpl(), but this would result in redundant checks.
andersca@apple.com's avatar
andersca@apple.com committed
323
        return jsString(exec, StringImpl::create(source.impl(), std::max(0, position), std::min(sourceSize, length)));
324 325
    }

326
    Checked<int, RecordOverflow> totalLength = 0;
327
    bool allSeparators8Bit = true;
328 329
    for (int i = 0; i < rangeCount; i++)
        totalLength += substringRanges[i].length;
330
    for (int i = 0; i < separatorCount; i++) {
331
        totalLength += separators[i].length();
332
        if (separators[i].length() && !separators[i].is8Bit())
333
            allSeparators8Bit = false;
334
    }
335 336
    if (totalLength.hasOverflowed())
        return throwOutOfMemoryError(exec);
337

338
    if (!totalLength)
339
        return jsEmptyString(exec);
340

341
    if (source.is8Bit() && allSeparators8Bit) {
342 343 344
        LChar* buffer;
        const LChar* sourceData = source.characters8();

345
        RefPtr<StringImpl> impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
346 347 348
        if (!impl)
            return throwOutOfMemoryError(exec);

andersca@apple.com's avatar
andersca@apple.com committed
349
        int maxCount = std::max(rangeCount, separatorCount);
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
        int bufferPos = 0;
        for (int i = 0; i < maxCount; i++) {
            if (i < rangeCount) {
                if (int srcLen = substringRanges[i].length) {
                    StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
                    bufferPos += srcLen;
                }
            }
            if (i < separatorCount) {
                if (int sepLen = separators[i].length()) {
                    StringImpl::copyChars(buffer + bufferPos, separators[i].characters8(), sepLen);
                    bufferPos += sepLen;
                }
            }
        }        

        return jsString(exec, impl.release());
    }

369
    UChar* buffer;
370
    RefPtr<StringImpl> impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
371 372 373
    if (!impl)
        return throwOutOfMemoryError(exec);

andersca@apple.com's avatar
andersca@apple.com committed
374
    int maxCount = std::max(rangeCount, separatorCount);
375 376 377
    int bufferPos = 0;
    for (int i = 0; i < maxCount; i++) {
        if (i < rangeCount) {
378
            if (int srcLen = substringRanges[i].length) {
379 380 381 382
                if (source.is8Bit())
                    StringImpl::copyChars(buffer + bufferPos, source.characters8() + substringRanges[i].position, srcLen);
                else
                    StringImpl::copyChars(buffer + bufferPos, source.characters16() + substringRanges[i].position, srcLen);
383 384
                bufferPos += srcLen;
            }
385 386
        }
        if (i < separatorCount) {
387
            if (int sepLen = separators[i].length()) {
388 389 390 391
                if (separators[i].is8Bit())
                    StringImpl::copyChars(buffer + bufferPos, separators[i].characters8(), sepLen);
                else
                    StringImpl::copyChars(buffer + bufferPos, separators[i].characters16(), sepLen);
392 393
                bufferPos += sepLen;
            }
394 395 396
        }
    }

397
    return jsString(exec, impl.release());
398 399
}

400
static NEVER_INLINE EncodedJSValue removeUsingRegExpSearch(ExecState* exec, JSString* string, const String& source, RegExp* regExp)
401
{
402
    size_t lastIndex = 0;
403 404 405
    unsigned startPosition = 0;

    Vector<StringRange, 16> sourceRanges;
ggaren@apple.com's avatar
ggaren@apple.com committed
406
    VM* vm = &exec->vm();
407 408 409 410
    RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
    unsigned sourceLen = source.length();

    while (true) {
ggaren@apple.com's avatar
ggaren@apple.com committed
411
        MatchResult result = regExpConstructor->performMatch(*vm, regExp, string, source, startPosition);
412
        if (!result)
413 414
            break;

415 416
        if (lastIndex < result.start)
            sourceRanges.append(StringRange(lastIndex, result.start - lastIndex));
417

418
        lastIndex = result.end;
419 420 421
        startPosition = lastIndex;

        // special case of empty match
422
        if (result.empty()) {
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
            startPosition++;
            if (startPosition > sourceLen)
                break;
        }
    }

    if (!lastIndex)
        return JSValue::encode(string);

    if (static_cast<unsigned>(lastIndex) < sourceLen)
        sourceRanges.append(StringRange(lastIndex, sourceLen - lastIndex));

    return JSValue::encode(jsSpliceSubstrings(exec, string, source, sourceRanges.data(), sourceRanges.size()));
}

438
static NEVER_INLINE EncodedJSValue replaceUsingRegExpSearch(ExecState* exec, JSString* string, JSValue searchValue)
mjs's avatar
mjs committed
439
{
440
    JSValue replaceValue = exec->argument(1);
441
    String replacementString;
442 443 444
    CallData callData;
    CallType callType = getCallData(replaceValue, callData);
    if (callType == CallTypeNone)
445
        replacementString = replaceValue.toString(exec)->value(exec);
weinig@apple.com's avatar
weinig@apple.com committed
446

447
    const String& source = string->value(exec);
448 449 450
    unsigned sourceLen = source.length();
    if (exec->hadException())
        return JSValue::encode(JSValue());
451 452
    RegExpObject* regExpObject = asRegExpObject(searchValue);
    RegExp* regExp = regExpObject->regExp();
453
    bool global = regExp->global();
weinig@apple.com's avatar
weinig@apple.com committed
454

455 456 457 458 459 460 461 462 463
    if (global) {
        // ES5.1 15.5.4.10 step 8.a.
        regExpObject->setLastIndex(exec, 0);
        if (exec->hadException())
            return JSValue::encode(JSValue());

        if (callType == CallTypeNone && !replacementString.length())
            return removeUsingRegExpSearch(exec, string, source, regExp);
    }
464

465
    RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
466

467
    size_t lastIndex = 0;
468
    unsigned startPosition = 0;
weinig@apple.com's avatar
weinig@apple.com committed
469

470
    Vector<StringRange, 16> sourceRanges;
471
    Vector<String, 16> replacements;
472

473 474 475 476
    // This is either a loop (if global is set) or a one-way (if not).
    if (global && callType == CallTypeJS) {
        // regExp->numSubpatterns() + 1 for pattern args, + 2 for match start and string
        int argCount = regExp->numSubpatterns() + 1 + 2;
477
        JSFunction* func = jsCast<JSFunction*>(replaceValue);
478 479 480
        CachedCall cachedCall(exec, func, argCount);
        if (exec->hadException())
            return JSValue::encode(jsNull());
ggaren@apple.com's avatar
ggaren@apple.com committed
481
        VM* vm = &exec->vm();
482 483 484
        if (source.is8Bit()) {
            while (true) {
                int* ovector;
ggaren@apple.com's avatar
ggaren@apple.com committed
485
                MatchResult result = regExpConstructor->performMatch(*vm, regExp, string, source, startPosition, &ovector);
486
                if (!result)
487
                    break;
weinig@apple.com's avatar
weinig@apple.com committed
488

489
                sourceRanges.append(StringRange(lastIndex, result.start - lastIndex));
490

491 492 493 494
                unsigned i = 0;
                for (; i < regExp->numSubpatterns() + 1; ++i) {
                    int matchStart = ovector[i * 2];
                    int matchLen = ovector[i * 2 + 1] - matchStart;
495

496 497 498
                    if (matchStart < 0)
                        cachedCall.setArgument(i, jsUndefined());
                    else
ggaren@apple.com's avatar
ggaren@apple.com committed
499
                        cachedCall.setArgument(i, jsSubstring8(vm, source, matchStart, matchLen));
weinig@apple.com's avatar
weinig@apple.com committed
500 501
                }

502
                cachedCall.setArgument(i++, jsNumber(result.start));
503
                cachedCall.setArgument(i++, string);
504

505
                cachedCall.setThis(jsUndefined());
506 507
                JSValue jsResult = cachedCall.call();
                replacements.append(jsResult.toString(cachedCall.newCallFrame(exec))->value(exec));
508 509
                if (exec->hadException())
                    break;
510

511
                lastIndex = result.end;
512
                startPosition = lastIndex;
513

514
                // special case of empty match
515
                if (result.empty()) {
516 517 518
                    startPosition++;
                    if (startPosition > sourceLen)
                        break;
519
                }
520
            }
521
        } else {
522
            while (true) {
523
                int* ovector;
ggaren@apple.com's avatar
ggaren@apple.com committed
524
                MatchResult result = regExpConstructor->performMatch(*vm, regExp, string, source, startPosition, &ovector);
525
                if (!result)
weinig@apple.com's avatar
weinig@apple.com committed
526
                    break;
527

528
                sourceRanges.append(StringRange(lastIndex, result.start - lastIndex));
529

530 531 532 533
                unsigned i = 0;
                for (; i < regExp->numSubpatterns() + 1; ++i) {
                    int matchStart = ovector[i * 2];
                    int matchLen = ovector[i * 2 + 1] - matchStart;
534

535 536 537
                    if (matchStart < 0)
                        cachedCall.setArgument(i, jsUndefined());
                    else
ggaren@apple.com's avatar
ggaren@apple.com committed
538
                        cachedCall.setArgument(i, jsSubstring(vm, source, matchStart, matchLen));
539
                }
540

541
                cachedCall.setArgument(i++, jsNumber(result.start));
542
                cachedCall.setArgument(i++, string);
543

544
                cachedCall.setThis(jsUndefined());
545 546
                JSValue jsResult = cachedCall.call();
                replacements.append(jsResult.toString(cachedCall.newCallFrame(exec))->value(exec));
547 548
                if (exec->hadException())
                    break;
549

550
                lastIndex = result.end;
551 552 553
                startPosition = lastIndex;

                // special case of empty match
554
                if (result.empty()) {
555
                    startPosition++;
556
                    if (startPosition > sourceLen)
557 558
                        break;
                }
559
            }
560
        }
561
    } else {
ggaren@apple.com's avatar
ggaren@apple.com committed
562
        VM* vm = &exec->vm();
563 564
        do {
            int* ovector;
ggaren@apple.com's avatar
ggaren@apple.com committed
565
            MatchResult result = regExpConstructor->performMatch(*vm, regExp, string, source, startPosition, &ovector);
566
            if (!result)
567 568 569
                break;

            if (callType != CallTypeNone) {
570
                sourceRanges.append(StringRange(lastIndex, result.start - lastIndex));
571 572 573 574 575 576 577 578 579 580 581 582

                MarkedArgumentBuffer args;

                for (unsigned i = 0; i < regExp->numSubpatterns() + 1; ++i) {
                    int matchStart = ovector[i * 2];
                    int matchLen = ovector[i * 2 + 1] - matchStart;

                    if (matchStart < 0)
                        args.append(jsUndefined());
                    else
                        args.append(jsSubstring(exec, source, matchStart, matchLen));
                }
darin's avatar
darin committed
583

584
                args.append(jsNumber(result.start));
585
                args.append(string);
mjs's avatar
mjs committed
586

587
                replacements.append(call(exec, replaceValue, callType, callData, jsUndefined(), args).toString(exec)->value(exec));
588 589 590 591
                if (exec->hadException())
                    break;
            } else {
                int replLen = replacementString.length();
592 593
                if (lastIndex < result.start || replLen) {
                    sourceRanges.append(StringRange(lastIndex, result.start - lastIndex));
594 595 596 597

                    if (replLen)
                        replacements.append(substituteBackreferences(replacementString, source, ovector, regExp));
                    else
598
                        replacements.append(String());
599 600 601
                }
            }

602
            lastIndex = result.end;
603
            startPosition = lastIndex;
604

605
            // special case of empty match
606
            if (result.empty()) {
607 608 609 610 611
                startPosition++;
                if (startPosition > sourceLen)
                    break;
            }
        } while (global);
weinig@apple.com's avatar
weinig@apple.com committed
612
    }
mjs's avatar
mjs committed
613

614 615
    if (!lastIndex && replacements.isEmpty())
        return JSValue::encode(string);
616

617 618
    if (static_cast<unsigned>(lastIndex) < sourceLen)
        sourceRanges.append(StringRange(lastIndex, sourceLen - lastIndex));
619

620 621 622
    return JSValue::encode(jsSpliceSubstringsWithSeparators(exec, string, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
}

623
static inline EncodedJSValue replaceUsingStringSearch(ExecState* exec, JSString* jsString, JSValue searchValue)
624
{
625 626
    const String& string = jsString->value(exec);
    String searchString = searchValue.toString(exec)->value(exec);
627 628 629
    if (exec->hadException())
        return JSValue::encode(jsUndefined());

630
    size_t matchStart = string.find(searchString);
631

632 633
    if (matchStart == notFound)
        return JSValue::encode(jsString);
634

635
    JSValue replaceValue = exec->argument(1);
636 637
    CallData callData;
    CallType callType = getCallData(replaceValue, callData);
weinig@apple.com's avatar
weinig@apple.com committed
638
    if (callType != CallTypeNone) {
639
        MarkedArgumentBuffer args;
640
        args.append(jsSubstring(exec, string, matchStart, searchString.impl()->length()));
641 642 643 644 645 646 647
        args.append(jsNumber(matchStart));
        args.append(jsString);
        replaceValue = call(exec, replaceValue, callType, callData, jsUndefined(), args);
        if (exec->hadException())
            return JSValue::encode(jsUndefined());
    }

648
    String replaceString = replaceValue.toString(exec)->value(exec);
649 650
    if (exec->hadException())
        return JSValue::encode(jsUndefined());
weinig@apple.com's avatar
weinig@apple.com committed
651

652
    StringImpl* stringImpl = string.impl();
653
    String leftPart(StringImpl::create(stringImpl, 0, matchStart));
654

655
    size_t matchEnd = matchStart + searchString.impl()->length();
656
    int ovector[2] = { static_cast<int>(matchStart),  static_cast<int>(matchEnd)};
657
    String middlePart = substituteBackreferences(replaceString, string, ovector, 0);
658 659

    size_t leftLength = stringImpl->length() - matchEnd;
660
    String rightPart(StringImpl::create(stringImpl, matchEnd, leftLength));
661
    return JSValue::encode(JSC::jsString(exec, leftPart, middlePart, rightPart));
662 663
}

664 665 666 667 668 669 670 671 672 673 674 675 676 677
static inline bool checkObjectCoercible(JSValue thisValue)
{
    if (thisValue.isString())
        return true;

    if (thisValue.isUndefinedOrNull())
        return false;

    if (thisValue.isCell() && thisValue.asCell()->structure()->typeInfo().isEnvironmentRecord())
        return false;

    return true;
}

678 679 680
EncodedJSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec)
{
    JSValue thisValue = exec->hostThisValue();
681
    if (!checkObjectCoercible(thisValue))
682 683
        return throwVMTypeError(exec);
    JSString* string = thisValue.toString(exec);
684 685
    JSValue searchValue = exec->argument(0);

686
    if (searchValue.inherits(RegExpObject::info()))
687 688
        return replaceUsingRegExpSearch(exec, string, searchValue);
    return replaceUsingStringSearch(exec, string, searchValue);
mjs's avatar
mjs committed
689 690
}

691
EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec)
692
{
693
    JSValue thisValue = exec->hostThisValue();
694
    // Also used for valueOf.
weinig@apple.com's avatar
weinig@apple.com committed
695

weinig@apple.com's avatar
weinig@apple.com committed
696
    if (thisValue.isString())
697
        return JSValue::encode(thisValue);
698

699
    if (thisValue.inherits(StringObject::info()))
700
        return JSValue::encode(asStringObject(thisValue)->internalValue());
701

702
    return throwVMTypeError(exec);
weinig@apple.com's avatar
weinig@apple.com committed
703
}
704

705
EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState* exec)
weinig@apple.com's avatar
weinig@apple.com committed
706
{
707
    JSValue thisValue = exec->hostThisValue();
708
    if (!checkObjectCoercible(thisValue))
709
        return throwVMTypeError(exec);
710
    String s = thisValue.toString(exec)->value(exec);
711
    unsigned len = s.length();
712
    JSValue a0 = exec->argument(0);
713 714
    if (a0.isUInt32()) {
        uint32_t i = a0.asUInt32();
barraclough@apple.com's avatar
barraclough@apple.com committed
715
        if (i < len)
716 717
            return JSValue::encode(jsSingleCharacterSubstring(exec, s, i));
        return JSValue::encode(jsEmptyString(exec));
darin@apple.com's avatar
darin@apple.com committed
718
    }
weinig@apple.com's avatar
weinig@apple.com committed
719
    double dpos = a0.toInteger(exec);
darin's avatar
darin committed
720
    if (dpos >= 0 && dpos < len)
721 722
        return JSValue::encode(jsSingleCharacterSubstring(exec, s, static_cast<unsigned>(dpos)));
    return JSValue::encode(jsEmptyString(exec));
weinig@apple.com's avatar
weinig@apple.com committed
723 724
}

725
EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState* exec)
weinig@apple.com's avatar
weinig@apple.com committed
726
{
727
    JSValue thisValue = exec->hostThisValue();
728
    if (!checkObjectCoercible(thisValue))
729
        return throwVMTypeError(exec);
730
    String s = thisValue.toString(exec)->value(exec);
731
    unsigned len = s.length();
732
    JSValue a0 = exec->argument(0);
733 734
    if (a0.isUInt32()) {
        uint32_t i = a0.asUInt32();
735 736 737 738 739
        if (i < len) {
            if (s.is8Bit())
                return JSValue::encode(jsNumber(s.characters8()[i]));
            return JSValue::encode(jsNumber(s.characters16()[i]));
        }
740
        return JSValue::encode(jsNaN());
darin@apple.com's avatar
darin@apple.com committed
741
    }
weinig@apple.com's avatar
weinig@apple.com committed
742
    double dpos = a0.toInteger(exec);
darin's avatar
darin committed
743
    if (dpos >= 0 && dpos < len)
744 745
        return JSValue::encode(jsNumber(s[static_cast<int>(dpos)]));
    return JSValue::encode(jsNaN());
weinig@apple.com's avatar
weinig@apple.com committed
746 747
}

748
EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState* exec)
weinig@apple.com's avatar
weinig@apple.com committed
749
{
750
    JSValue thisValue = exec->hostThisValue();
751 752
    if (thisValue.isString() && exec->argumentCount() == 1)
        return JSValue::encode(jsString(exec, asString(thisValue), exec->uncheckedArgument(0).toString(exec)));
753

754
    if (!checkObjectCoercible(thisValue))
755
        return throwVMTypeError(exec);
756
    return JSValue::encode(jsStringFromArguments(exec, thisValue));
weinig@apple.com's avatar
weinig@apple.com committed
757 758
}

759
EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState* exec)
weinig@apple.com's avatar
weinig@apple.com committed
760
{
761
    JSValue thisValue = exec->hostThisValue();
762
    if (!checkObjectCoercible(thisValue))
763
        return throwVMTypeError(exec);
764
    String s = thisValue.toString(exec)->value(exec);
weinig@apple.com's avatar
weinig@apple.com committed
765

766 767
    JSValue a0 = exec->argument(0);
    JSValue a1 = exec->argument(1);
768
    String u2 = a0.toString(exec)->value(exec);
769 770

    size_t result;
771
    if (a1.isUndefined())
772
        result = s.find(u2);
773
    else {
benjamin@webkit.org's avatar