2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3 * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
4 * Copyright (C) 2009 Torch Mobile, Inc.
5 * Copyright (C) 2015 Jordan Harband (ljharb@gmail.com)
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "StringPrototype.h"
26 #include "BuiltinNames.h"
27 #include "ButterflyInlines.h"
28 #include "CachedCall.h"
30 #include "IntlObject.h"
32 #include "JSCBuiltins.h"
33 #include "JSCInlines.h"
34 #include "JSFunction.h"
35 #include "JSGlobalObjectFunctions.h"
36 #include "JSStringBuilder.h"
37 #include "JSStringIterator.h"
39 #include "ObjectPrototype.h"
41 #include "PropertyNameArray.h"
42 #include "RegExpCache.h"
43 #include "RegExpConstructor.h"
44 #include "RegExpObject.h"
45 #include "SuperSampler.h"
47 #include <unicode/uconfig.h>
48 #include <unicode/unorm.h>
49 #include <unicode/ustring.h>
50 #include <wtf/ASCIICType.h>
51 #include <wtf/MathExtras.h>
52 #include <wtf/text/StringBuilder.h>
53 #include <wtf/text/StringView.h>
54 #include <wtf/unicode/Collator.h>
60 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(StringPrototype);
62 EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState*);
63 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState*);
64 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState*);
65 EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState*);
66 EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState*);
67 EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState*);
68 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*);
69 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState*);
70 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState*);
71 EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState*);
72 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState*);
73 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState*);
74 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState*);
75 EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState*);
76 EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState*);
77 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleLowerCase(ExecState*);
78 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleUpperCase(ExecState*);
79 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState*);
80 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState*);
81 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState*);
82 EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState*);
83 EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState*);
84 EncodedJSValue JSC_HOST_CALL stringProtoFuncIncludes(ExecState*);
85 EncodedJSValue JSC_HOST_CALL stringProtoFuncNormalize(ExecState*);
86 EncodedJSValue JSC_HOST_CALL stringProtoFuncIterator(ExecState*);
90 #include "StringPrototype.lut.h"
94 const ClassInfo StringPrototype::s_info = { "String", &StringObject::s_info, &stringPrototypeTable, nullptr, CREATE_METHOD_TABLE(StringPrototype) };
96 /* Source for StringConstructor.lut.h
97 @begin stringPrototypeTable
98 match JSBuiltin DontEnum|Function 1
99 padStart JSBuiltin DontEnum|Function 1
100 padEnd JSBuiltin DontEnum|Function 1
101 repeat JSBuiltin DontEnum|Function 1
102 replace JSBuiltin DontEnum|Function 2
103 search JSBuiltin DontEnum|Function 1
104 split JSBuiltin DontEnum|Function 1
105 anchor JSBuiltin DontEnum|Function 1
106 big JSBuiltin DontEnum|Function 0
107 bold JSBuiltin DontEnum|Function 0
108 blink JSBuiltin DontEnum|Function 0
109 fixed JSBuiltin DontEnum|Function 0
110 fontcolor JSBuiltin DontEnum|Function 1
111 fontsize JSBuiltin DontEnum|Function 1
112 italics JSBuiltin DontEnum|Function 0
113 link JSBuiltin DontEnum|Function 1
114 small JSBuiltin DontEnum|Function 0
115 strike JSBuiltin DontEnum|Function 0
116 sub JSBuiltin DontEnum|Function 0
117 sup JSBuiltin DontEnum|Function 0
122 StringPrototype::StringPrototype(VM& vm, Structure* structure)
123 : StringObject(vm, structure)
127 void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSString* nameAndMessage)
129 Base::finishCreation(vm, nameAndMessage);
130 ASSERT(inherits(vm, info()));
132 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, stringProtoFuncToString, DontEnum, 0, StringPrototypeValueOfIntrinsic);
133 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->valueOf, stringProtoFuncToString, DontEnum, 0, StringPrototypeValueOfIntrinsic);
134 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("charAt", stringProtoFuncCharAt, DontEnum, 1, CharAtIntrinsic);
135 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("charCodeAt", stringProtoFuncCharCodeAt, DontEnum, 1, CharCodeAtIntrinsic);
136 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("codePointAt", stringProtoFuncCodePointAt, DontEnum, 1);
137 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("concat", stringProtoFuncConcat, DontEnum, 1);
138 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("indexOf", stringProtoFuncIndexOf, DontEnum, 1);
139 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", stringProtoFuncLastIndexOf, DontEnum, 1);
140 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingRegExpPrivateName(), stringProtoFuncReplaceUsingRegExp, DontEnum, 2, StringPrototypeReplaceRegExpIntrinsic);
141 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingStringSearchPrivateName(), stringProtoFuncReplaceUsingStringSearch, DontEnum, 2);
142 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, DontEnum, 2);
143 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substr", stringProtoFuncSubstr, DontEnum, 2);
144 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substring", stringProtoFuncSubstring, DontEnum, 2);
145 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0, StringPrototypeToLowerCaseIntrinsic);
146 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
148 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringPrototypeLocaleCompareCodeGenerator, DontEnum);
149 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleLowerCase", stringProtoFuncToLocaleLowerCase, DontEnum, 0);
150 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleUpperCase", stringProtoFuncToLocaleUpperCase, DontEnum, 0);
152 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringProtoFuncLocaleCompare, DontEnum, 1);
153 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleLowerCase", stringProtoFuncToLowerCase, DontEnum, 0);
154 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
156 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trim", stringProtoFuncTrim, DontEnum, 0);
157 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trimLeft", stringProtoFuncTrimLeft, DontEnum, 0);
158 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trimRight", stringProtoFuncTrimRight, DontEnum, 0);
159 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("startsWith", stringProtoFuncStartsWith, DontEnum, 1);
160 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("endsWith", stringProtoFuncEndsWith, DontEnum, 1);
161 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("includes", stringProtoFuncIncludes, DontEnum, 1);
162 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("normalize", stringProtoFuncNormalize, DontEnum, 0);
163 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().charCodeAtPrivateName(), stringProtoFuncCharCodeAt, DontEnum, 1, CharCodeAtIntrinsic);
165 JSFunction* iteratorFunction = JSFunction::create(vm, globalObject, 0, ASCIILiteral("[Symbol.iterator]"), stringProtoFuncIterator, NoIntrinsic);
166 putDirectWithoutTransition(vm, vm.propertyNames->iteratorSymbol, iteratorFunction, DontEnum);
168 // The constructor will be added later, after StringConstructor has been built
169 putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
172 StringPrototype* StringPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
174 JSString* empty = jsEmptyString(&vm);
175 StringPrototype* prototype = new (NotNull, allocateCell<StringPrototype>(vm.heap)) StringPrototype(vm, structure);
176 prototype->finishCreation(vm, globalObject, empty);
180 // ------------------------------ Functions --------------------------
182 static NEVER_INLINE String substituteBackreferencesSlow(StringView replacement, StringView source, const int* ovector, RegExp* reg, size_t i)
184 StringBuilder substitutedReplacement;
187 if (i + 1 == replacement.length())
190 UChar ref = replacement[i + 1];
194 substitutedReplacement.append(replacement.substring(offset, i - offset));
203 backrefStart = ovector[0];
204 backrefLength = ovector[1] - backrefStart;
205 } else if (ref == '`') {
207 backrefLength = ovector[0];
208 } else if (ref == '\'') {
209 backrefStart = ovector[1];
210 backrefLength = source.length() - backrefStart;
211 } else if (reg && isASCIIDigit(ref)) {
212 // 1- and 2-digit back references are allowed
213 unsigned backrefIndex = ref - '0';
214 if (backrefIndex > reg->numSubpatterns())
216 if (replacement.length() > i + 2) {
217 ref = replacement[i + 2];
218 if (isASCIIDigit(ref)) {
219 backrefIndex = 10 * backrefIndex + ref - '0';
220 if (backrefIndex > reg->numSubpatterns())
221 backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference
228 backrefStart = ovector[2 * backrefIndex];
229 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
234 substitutedReplacement.append(replacement.substring(offset, i - offset));
237 if (backrefStart >= 0)
238 substitutedReplacement.append(source.substring(backrefStart, backrefLength));
239 } while ((i = replacement.find('$', i + 1)) != notFound);
241 if (replacement.length() - offset)
242 substitutedReplacement.append(replacement.substring(offset));
244 return substitutedReplacement.toString();
247 inline String substituteBackreferencesInline(const String& replacement, StringView source, const int* ovector, RegExp* reg)
249 size_t i = replacement.find('$');
250 if (UNLIKELY(i != notFound))
251 return substituteBackreferencesSlow(replacement, source, ovector, reg, i);
256 String substituteBackreferences(const String& replacement, StringView source, const int* ovector, RegExp* reg)
258 return substituteBackreferencesInline(replacement, source, ovector, reg);
262 StringRange(int pos, int len)
276 static ALWAYS_INLINE JSValue jsSpliceSubstrings(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount)
279 auto scope = DECLARE_THROW_SCOPE(vm);
281 if (rangeCount == 1) {
282 int sourceSize = source.length();
283 int position = substringRanges[0].position;
284 int length = substringRanges[0].length;
285 if (position <= 0 && length >= sourceSize)
287 // We could call String::substringSharingImpl(), but this would result in redundant checks.
289 return jsString(exec, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length)));
293 for (int i = 0; i < rangeCount; i++)
294 totalLength += substringRanges[i].length;
297 return jsEmptyString(exec);
299 if (source.is8Bit()) {
301 const LChar* sourceData = source.characters8();
302 auto impl = StringImpl::tryCreateUninitialized(totalLength, buffer);
304 return throwOutOfMemoryError(exec, scope);
307 for (int i = 0; i < rangeCount; i++) {
308 if (int srcLen = substringRanges[i].length) {
309 StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
315 return jsString(exec, WTFMove(impl));
319 const UChar* sourceData = source.characters16();
321 auto impl = StringImpl::tryCreateUninitialized(totalLength, buffer);
323 return throwOutOfMemoryError(exec, scope);
326 for (int i = 0; i < rangeCount; i++) {
327 if (int srcLen = substringRanges[i].length) {
328 StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
334 return jsString(exec, WTFMove(impl));
337 static ALWAYS_INLINE JSValue jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount, const String* separators, int separatorCount)
340 auto scope = DECLARE_THROW_SCOPE(vm);
342 if (rangeCount == 1 && separatorCount == 0) {
343 int sourceSize = source.length();
344 int position = substringRanges[0].position;
345 int length = substringRanges[0].length;
346 if (position <= 0 && length >= sourceSize)
348 // We could call String::substringSharingImpl(), but this would result in redundant checks.
350 return jsString(exec, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length)));
353 Checked<int, RecordOverflow> totalLength = 0;
354 bool allSeparators8Bit = true;
355 for (int i = 0; i < rangeCount; i++)
356 totalLength += substringRanges[i].length;
357 for (int i = 0; i < separatorCount; i++) {
358 totalLength += separators[i].length();
359 if (separators[i].length() && !separators[i].is8Bit())
360 allSeparators8Bit = false;
362 if (totalLength.hasOverflowed())
363 return throwOutOfMemoryError(exec, scope);
366 return jsEmptyString(exec);
368 if (source.is8Bit() && allSeparators8Bit) {
370 const LChar* sourceData = source.characters8();
372 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
374 return throwOutOfMemoryError(exec, scope);
376 int maxCount = std::max(rangeCount, separatorCount);
378 for (int i = 0; i < maxCount; i++) {
379 if (i < rangeCount) {
380 if (int srcLen = substringRanges[i].length) {
381 StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
385 if (i < separatorCount) {
386 if (int sepLen = separators[i].length()) {
387 StringImpl::copyChars(buffer + bufferPos, separators[i].characters8(), sepLen);
394 return jsString(exec, WTFMove(impl));
398 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
400 return throwOutOfMemoryError(exec, scope);
402 int maxCount = std::max(rangeCount, separatorCount);
404 for (int i = 0; i < maxCount; i++) {
405 if (i < rangeCount) {
406 if (int srcLen = substringRanges[i].length) {
408 StringImpl::copyChars(buffer + bufferPos, source.characters8() + substringRanges[i].position, srcLen);
410 StringImpl::copyChars(buffer + bufferPos, source.characters16() + substringRanges[i].position, srcLen);
414 if (i < separatorCount) {
415 if (int sepLen = separators[i].length()) {
416 if (separators[i].is8Bit())
417 StringImpl::copyChars(buffer + bufferPos, separators[i].characters8(), sepLen);
419 StringImpl::copyChars(buffer + bufferPos, separators[i].characters16(), sepLen);
426 return jsString(exec, WTFMove(impl));
429 #define OUT_OF_MEMORY(exec__, scope__) \
431 throwOutOfMemoryError(exec__, scope__); \
432 return encodedJSValue(); \
435 static ALWAYS_INLINE EncodedJSValue removeUsingRegExpSearch(VM& vm, ExecState* exec, JSString* string, const String& source, RegExp* regExp)
437 auto scope = DECLARE_THROW_SCOPE(vm);
438 SuperSamplerScope superSamplerScope(false);
440 size_t lastIndex = 0;
441 unsigned startPosition = 0;
443 Vector<StringRange, 16> sourceRanges;
444 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
445 unsigned sourceLen = source.length();
448 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition);
452 if (lastIndex < result.start) {
453 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
454 OUT_OF_MEMORY(exec, scope);
456 lastIndex = result.end;
457 startPosition = lastIndex;
459 // special case of empty match
460 if (result.empty()) {
462 if (startPosition > sourceLen)
468 return JSValue::encode(string);
470 if (static_cast<unsigned>(lastIndex) < sourceLen) {
471 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, sourceLen - lastIndex)))
472 OUT_OF_MEMORY(exec, scope);
475 return JSValue::encode(jsSpliceSubstrings(exec, string, source, sourceRanges.data(), sourceRanges.size()));
478 static ALWAYS_INLINE EncodedJSValue replaceUsingRegExpSearch(
479 VM& vm, ExecState* exec, JSString* string, JSValue searchValue, CallData& callData,
480 CallType callType, String& replacementString, JSValue replaceValue)
482 auto scope = DECLARE_THROW_SCOPE(vm);
484 const String& source = string->value(exec);
485 unsigned sourceLen = source.length();
486 RETURN_IF_EXCEPTION(scope, encodedJSValue());
487 RegExpObject* regExpObject = asRegExpObject(searchValue);
488 RegExp* regExp = regExpObject->regExp();
489 bool global = regExp->global();
492 // ES5.1 15.5.4.10 step 8.a.
493 regExpObject->setLastIndex(exec, 0);
494 RETURN_IF_EXCEPTION(scope, encodedJSValue());
496 if (callType == CallType::None && !replacementString.length()) {
498 return removeUsingRegExpSearch(vm, exec, string, source, regExp);
502 // FIXME: This is wrong because we may be called directly from the FTL.
503 // https://bugs.webkit.org/show_bug.cgi?id=154874
504 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
506 size_t lastIndex = 0;
507 unsigned startPosition = 0;
509 Vector<StringRange, 16> sourceRanges;
510 Vector<String, 16> replacements;
512 // This is either a loop (if global is set) or a one-way (if not).
513 if (global && callType == CallType::JS) {
514 // regExp->numSubpatterns() + 1 for pattern args, + 2 for match start and string
515 int argCount = regExp->numSubpatterns() + 1 + 2;
516 JSFunction* func = jsCast<JSFunction*>(replaceValue);
517 CachedCall cachedCall(exec, func, argCount);
518 RETURN_IF_EXCEPTION(scope, encodedJSValue());
519 if (source.is8Bit()) {
522 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition, &ovector);
526 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
527 OUT_OF_MEMORY(exec, scope);
530 cachedCall.clearArguments();
531 for (; i < regExp->numSubpatterns() + 1; ++i) {
532 int matchStart = ovector[i * 2];
533 int matchLen = ovector[i * 2 + 1] - matchStart;
536 cachedCall.appendArgument(jsUndefined());
538 cachedCall.appendArgument(jsSubstring(&vm, source, matchStart, matchLen));
541 cachedCall.appendArgument(jsNumber(result.start));
542 cachedCall.appendArgument(string);
544 cachedCall.setThis(jsUndefined());
545 JSValue jsResult = cachedCall.call();
546 RETURN_IF_EXCEPTION(scope, encodedJSValue());
547 replacements.append(jsResult.toWTFString(exec));
548 RETURN_IF_EXCEPTION(scope, encodedJSValue());
550 lastIndex = result.end;
551 startPosition = lastIndex;
553 // special case of empty match
554 if (result.empty()) {
556 if (startPosition > sourceLen)
563 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition, &ovector);
567 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
568 OUT_OF_MEMORY(exec, scope);
571 cachedCall.clearArguments();
572 for (; i < regExp->numSubpatterns() + 1; ++i) {
573 int matchStart = ovector[i * 2];
574 int matchLen = ovector[i * 2 + 1] - matchStart;
577 cachedCall.appendArgument(jsUndefined());
579 cachedCall.appendArgument(jsSubstring(&vm, source, matchStart, matchLen));
582 cachedCall.appendArgument(jsNumber(result.start));
583 cachedCall.appendArgument(string);
585 cachedCall.setThis(jsUndefined());
586 JSValue jsResult = cachedCall.call();
587 RETURN_IF_EXCEPTION(scope, encodedJSValue());
588 replacements.append(jsResult.toWTFString(exec));
589 RETURN_IF_EXCEPTION(scope, encodedJSValue());
591 lastIndex = result.end;
592 startPosition = lastIndex;
594 // special case of empty match
595 if (result.empty()) {
597 if (startPosition > sourceLen)
605 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition, &ovector);
609 if (callType != CallType::None) {
610 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
611 OUT_OF_MEMORY(exec, scope);
613 MarkedArgumentBuffer args;
615 for (unsigned i = 0; i < regExp->numSubpatterns() + 1; ++i) {
616 int matchStart = ovector[i * 2];
617 int matchLen = ovector[i * 2 + 1] - matchStart;
620 args.append(jsUndefined());
622 args.append(jsSubstring(exec, source, matchStart, matchLen));
625 args.append(jsNumber(result.start));
628 JSValue replacement = call(exec, replaceValue, callType, callData, jsUndefined(), args);
629 RETURN_IF_EXCEPTION(scope, encodedJSValue());
630 String replacementString = replacement.toWTFString(exec);
631 RETURN_IF_EXCEPTION(scope, encodedJSValue());
632 replacements.append(replacementString);
633 RETURN_IF_EXCEPTION(scope, encodedJSValue());
635 int replLen = replacementString.length();
636 if (lastIndex < result.start || replLen) {
637 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
638 OUT_OF_MEMORY(exec, scope);
641 replacements.append(substituteBackreferences(replacementString, source, ovector, regExp));
643 replacements.append(String());
647 lastIndex = result.end;
648 startPosition = lastIndex;
650 // special case of empty match
651 if (result.empty()) {
653 if (startPosition > sourceLen)
659 if (!lastIndex && replacements.isEmpty())
660 return JSValue::encode(string);
662 if (static_cast<unsigned>(lastIndex) < sourceLen) {
663 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, sourceLen - lastIndex)))
664 OUT_OF_MEMORY(exec, scope);
667 return JSValue::encode(jsSpliceSubstringsWithSeparators(exec, string, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
670 EncodedJSValue JIT_OPERATION operationStringProtoFuncReplaceRegExpEmptyStr(
671 ExecState* exec, JSString* thisValue, RegExpObject* searchValue)
674 NativeCallFrameTracer tracer(&vm, exec);
675 auto scope = DECLARE_THROW_SCOPE(vm);
677 RegExp* regExp = searchValue->regExp();
678 if (regExp->global()) {
679 // ES5.1 15.5.4.10 step 8.a.
680 searchValue->setLastIndex(exec, 0);
681 RETURN_IF_EXCEPTION(scope, encodedJSValue());
683 return removeUsingRegExpSearch(vm, exec, thisValue, thisValue->value(exec), regExp);
687 String replacementString = emptyString();
689 return replaceUsingRegExpSearch(
690 vm, exec, thisValue, searchValue, callData, CallType::None, replacementString, JSValue());
693 EncodedJSValue JIT_OPERATION operationStringProtoFuncReplaceRegExpString(
694 ExecState* exec, JSString* thisValue, RegExpObject* searchValue, JSString* replaceString)
697 NativeCallFrameTracer tracer(&vm, exec);
700 String replacementString = replaceString->value(exec);
701 return replaceUsingRegExpSearch(
702 vm, exec, thisValue, searchValue, callData, CallType::None, replacementString, replaceString);
705 static ALWAYS_INLINE EncodedJSValue replaceUsingRegExpSearch(VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
707 auto scope = DECLARE_THROW_SCOPE(vm);
709 String replacementString;
711 CallType callType = getCallData(replaceValue, callData);
712 if (callType == CallType::None) {
713 replacementString = replaceValue.toWTFString(exec);
714 RETURN_IF_EXCEPTION(scope, encodedJSValue());
718 return replaceUsingRegExpSearch(
719 vm, exec, string, searchValue, callData, callType, replacementString, replaceValue);
722 static ALWAYS_INLINE EncodedJSValue replaceUsingStringSearch(VM& vm, ExecState* exec, JSString* jsString, JSValue searchValue, JSValue replaceValue)
724 auto scope = DECLARE_THROW_SCOPE(vm);
726 const String& string = jsString->value(exec);
727 RETURN_IF_EXCEPTION(scope, encodedJSValue());
728 String searchString = searchValue.toWTFString(exec);
729 RETURN_IF_EXCEPTION(scope, encodedJSValue());
731 size_t matchStart = string.find(searchString);
733 if (matchStart == notFound)
734 return JSValue::encode(jsString);
737 CallType callType = getCallData(replaceValue, callData);
738 if (callType != CallType::None) {
739 MarkedArgumentBuffer args;
740 args.append(jsSubstring(exec, string, matchStart, searchString.impl()->length()));
741 args.append(jsNumber(matchStart));
742 args.append(jsString);
743 replaceValue = call(exec, replaceValue, callType, callData, jsUndefined(), args);
744 RETURN_IF_EXCEPTION(scope, encodedJSValue());
747 String replaceString = replaceValue.toWTFString(exec);
748 RETURN_IF_EXCEPTION(scope, encodedJSValue());
750 StringImpl* stringImpl = string.impl();
751 String leftPart(StringImpl::createSubstringSharingImpl(*stringImpl, 0, matchStart));
753 size_t matchEnd = matchStart + searchString.impl()->length();
754 int ovector[2] = { static_cast<int>(matchStart), static_cast<int>(matchEnd)};
755 String middlePart = callType != CallType::None ? replaceString : substituteBackreferences(replaceString, string, ovector, 0);
757 size_t leftLength = stringImpl->length() - matchEnd;
758 String rightPart(StringImpl::createSubstringSharingImpl(*stringImpl, matchEnd, leftLength));
760 return JSValue::encode(JSC::jsString(exec, leftPart, middlePart, rightPart));
763 static inline bool checkObjectCoercible(JSValue thisValue)
765 if (thisValue.isString())
768 if (thisValue.isUndefinedOrNull())
771 if (thisValue.isObject() && asObject(thisValue)->isEnvironmentRecord())
777 template <typename CharacterType>
778 static inline JSString* repeatCharacter(ExecState& exec, CharacterType character, unsigned repeatCount)
781 auto scope = DECLARE_THROW_SCOPE(vm);
783 CharacterType* buffer = nullptr;
784 auto impl = StringImpl::tryCreateUninitialized(repeatCount, buffer);
786 throwOutOfMemoryError(&exec, scope);
790 std::fill_n(buffer, repeatCount, character);
793 return jsString(&exec, WTFMove(impl));
796 EncodedJSValue JSC_HOST_CALL stringProtoFuncRepeatCharacter(ExecState* exec)
799 auto scope = DECLARE_THROW_SCOPE(vm);
801 // For a string which length is single, instead of creating ropes,
802 // allocating a sequential buffer and fill with the repeated string for efficiency.
803 ASSERT(exec->argumentCount() == 2);
805 ASSERT(exec->uncheckedArgument(0).isString());
806 JSString* string = asString(exec->uncheckedArgument(0));
807 ASSERT(string->length() == 1);
809 JSValue repeatCountValue = exec->uncheckedArgument(1);
810 RELEASE_ASSERT(repeatCountValue.isNumber());
812 double value = repeatCountValue.asNumber();
813 if (value > JSString::MaxLength)
814 return JSValue::encode(throwOutOfMemoryError(exec, scope));
815 repeatCount = static_cast<int32_t>(value);
816 ASSERT(repeatCount >= 0);
817 ASSERT(!repeatCountValue.isDouble() || repeatCountValue.asDouble() == repeatCount);
819 auto viewWithString = string->viewWithUnderlyingString(exec);
820 StringView view = viewWithString.view;
821 ASSERT(view.length() == 1);
822 scope.assertNoException();
823 UChar character = view[0];
825 if (!(character & ~0xff))
826 return JSValue::encode(repeatCharacter(*exec, static_cast<LChar>(character), repeatCount));
827 return JSValue::encode(repeatCharacter(*exec, character, repeatCount));
830 ALWAYS_INLINE EncodedJSValue replace(
831 VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
833 if (searchValue.inherits(vm, RegExpObject::info()))
834 return replaceUsingRegExpSearch(vm, exec, string, searchValue, replaceValue);
835 return replaceUsingStringSearch(vm, exec, string, searchValue, replaceValue);
838 ALWAYS_INLINE EncodedJSValue replace(
839 VM& vm, ExecState* exec, JSValue thisValue, JSValue searchValue, JSValue replaceValue)
841 auto scope = DECLARE_THROW_SCOPE(vm);
843 if (!checkObjectCoercible(thisValue))
844 return throwVMTypeError(exec, scope);
845 JSString* string = thisValue.toString(exec);
846 RETURN_IF_EXCEPTION(scope, encodedJSValue());
848 return replace(vm, exec, string, searchValue, replaceValue);
851 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState* exec)
854 auto scope = DECLARE_THROW_SCOPE(vm);
856 JSString* string = exec->thisValue().toString(exec);
857 RETURN_IF_EXCEPTION(scope, encodedJSValue());
859 JSValue searchValue = exec->argument(0);
860 if (!searchValue.inherits(vm, RegExpObject::info()))
861 return JSValue::encode(jsUndefined());
864 return replaceUsingRegExpSearch(vm, exec, string, searchValue, exec->argument(1));
867 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState* exec)
870 auto scope = DECLARE_THROW_SCOPE(vm);
872 JSString* string = exec->thisValue().toString(exec);
873 RETURN_IF_EXCEPTION(scope, encodedJSValue());
876 return replaceUsingStringSearch(vm, exec, string, exec->argument(0), exec->argument(1));
879 EncodedJSValue JIT_OPERATION operationStringProtoFuncReplaceGeneric(
880 ExecState* exec, EncodedJSValue thisValue, EncodedJSValue searchValue,
881 EncodedJSValue replaceValue)
884 NativeCallFrameTracer tracer(&vm, exec);
887 vm, exec, JSValue::decode(thisValue), JSValue::decode(searchValue),
888 JSValue::decode(replaceValue));
891 EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec)
894 auto scope = DECLARE_THROW_SCOPE(vm);
896 JSValue thisValue = exec->thisValue();
897 // Also used for valueOf.
899 if (thisValue.isString())
900 return JSValue::encode(thisValue);
902 if (thisValue.inherits(vm, StringObject::info()))
903 return JSValue::encode(asStringObject(thisValue)->internalValue());
905 return throwVMTypeError(exec, scope);
908 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState* exec)
911 auto scope = DECLARE_THROW_SCOPE(vm);
913 JSValue thisValue = exec->thisValue();
914 if (!checkObjectCoercible(thisValue))
915 return throwVMTypeError(exec, scope);
916 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
917 RETURN_IF_EXCEPTION(scope, encodedJSValue());
918 StringView view = viewWithString.view;
919 RETURN_IF_EXCEPTION(scope, encodedJSValue());
920 JSValue a0 = exec->argument(0);
922 uint32_t i = a0.asUInt32();
923 if (i < view.length())
924 return JSValue::encode(jsSingleCharacterString(exec, view[i]));
925 return JSValue::encode(jsEmptyString(exec));
927 double dpos = a0.toInteger(exec);
928 if (dpos >= 0 && dpos < view.length())
929 return JSValue::encode(jsSingleCharacterString(exec, view[static_cast<unsigned>(dpos)]));
930 return JSValue::encode(jsEmptyString(exec));
933 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState* exec)
936 auto scope = DECLARE_THROW_SCOPE(vm);
938 JSValue thisValue = exec->thisValue();
939 if (!checkObjectCoercible(thisValue))
940 return throwVMTypeError(exec, scope);
941 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
942 RETURN_IF_EXCEPTION(scope, encodedJSValue());
943 StringView view = viewWithString.view;
944 JSValue a0 = exec->argument(0);
946 uint32_t i = a0.asUInt32();
947 if (i < view.length())
948 return JSValue::encode(jsNumber(view[i]));
949 return JSValue::encode(jsNaN());
951 double dpos = a0.toInteger(exec);
952 if (dpos >= 0 && dpos < view.length())
953 return JSValue::encode(jsNumber(view[static_cast<int>(dpos)]));
954 return JSValue::encode(jsNaN());
957 static inline UChar32 codePointAt(const String& string, unsigned position, unsigned length)
959 RELEASE_ASSERT(position < length);
961 return string.characters8()[position];
963 U16_NEXT(string.characters16(), position, length, character);
967 EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState* exec)
970 auto scope = DECLARE_THROW_SCOPE(vm);
972 JSValue thisValue = exec->thisValue();
973 if (!checkObjectCoercible(thisValue))
974 return throwVMTypeError(exec, scope);
976 String string = thisValue.toWTFString(exec);
977 RETURN_IF_EXCEPTION(scope, encodedJSValue());
978 unsigned length = string.length();
980 JSValue argument0 = exec->argument(0);
981 if (argument0.isUInt32()) {
982 unsigned position = argument0.asUInt32();
983 if (position < length)
984 return JSValue::encode(jsNumber(codePointAt(string, position, length)));
985 return JSValue::encode(jsUndefined());
988 RETURN_IF_EXCEPTION(scope, encodedJSValue());
990 double doublePosition = argument0.toInteger(exec);
991 RETURN_IF_EXCEPTION(scope, encodedJSValue());
992 if (doublePosition >= 0 && doublePosition < length)
993 return JSValue::encode(jsNumber(codePointAt(string, static_cast<unsigned>(doublePosition), length)));
994 return JSValue::encode(jsUndefined());
997 EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState* exec)
1000 auto scope = DECLARE_THROW_SCOPE(vm);
1002 JSValue thisValue = exec->thisValue();
1003 if (thisValue.isString() && exec->argumentCount() == 1) {
1004 JSString* str = exec->uncheckedArgument(0).toString(exec);
1005 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1007 return JSValue::encode(jsString(exec, asString(thisValue), str));
1010 if (!checkObjectCoercible(thisValue))
1011 return throwVMTypeError(exec, scope);
1013 return JSValue::encode(jsStringFromArguments(exec, thisValue));
1016 EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState* exec)
1018 VM& vm = exec->vm();
1019 auto scope = DECLARE_THROW_SCOPE(vm);
1021 JSValue thisValue = exec->thisValue();
1022 if (!checkObjectCoercible(thisValue))
1023 return throwVMTypeError(exec, scope);
1025 JSValue a0 = exec->argument(0);
1026 JSValue a1 = exec->argument(1);
1028 JSString* thisJSString = thisValue.toString(exec);
1029 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1030 JSString* otherJSString = a0.toString(exec);
1031 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1034 if (!a1.isUndefined()) {
1035 int len = thisJSString->length();
1036 RELEASE_ASSERT(len >= 0);
1038 pos = std::min<uint32_t>(a1.asUInt32(), len);
1040 double dpos = a1.toInteger(exec);
1043 else if (dpos > len)
1045 pos = static_cast<unsigned>(dpos);
1049 if (thisJSString->length() < otherJSString->length() + pos)
1050 return JSValue::encode(jsNumber(-1));
1052 auto thisViewWithString = thisJSString->viewWithUnderlyingString(exec);
1053 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1054 auto otherViewWithString = otherJSString->viewWithUnderlyingString(exec);
1055 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1056 size_t result = thisViewWithString.view.find(otherViewWithString.view, pos);
1057 if (result == notFound)
1058 return JSValue::encode(jsNumber(-1));
1059 return JSValue::encode(jsNumber(result));
1062 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState* exec)
1064 VM& vm = exec->vm();
1065 auto scope = DECLARE_THROW_SCOPE(vm);
1067 JSValue thisValue = exec->thisValue();
1068 if (!checkObjectCoercible(thisValue))
1069 return throwVMTypeError(exec, scope);
1071 JSValue a0 = exec->argument(0);
1072 JSValue a1 = exec->argument(1);
1074 JSString* thisJSString = thisValue.toString(exec);
1075 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1076 unsigned len = thisJSString->length();
1077 JSString* otherJSString = a0.toString(exec);
1078 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1080 double dpos = a1.toIntegerPreserveNaN(exec);
1081 unsigned startPosition;
1084 else if (!(dpos <= len)) // true for NaN
1085 startPosition = len;
1087 startPosition = static_cast<unsigned>(dpos);
1089 if (len < otherJSString->length())
1090 return JSValue::encode(jsNumber(-1));
1092 String thisString = thisJSString->value(exec);
1093 String otherString = otherJSString->value(exec);
1096 result = thisString.startsWith(otherString) ? 0 : notFound;
1098 result = thisString.reverseFind(otherString, startPosition);
1099 if (result == notFound)
1100 return JSValue::encode(jsNumber(-1));
1101 return JSValue::encode(jsNumber(result));
1104 EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState* exec)
1106 VM& vm = exec->vm();
1107 auto scope = DECLARE_THROW_SCOPE(vm);
1109 JSValue thisValue = exec->thisValue();
1110 if (!checkObjectCoercible(thisValue))
1111 return throwVMTypeError(exec, scope);
1112 String s = thisValue.toWTFString(exec);
1113 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1115 int len = s.length();
1116 RELEASE_ASSERT(len >= 0);
1118 JSValue a0 = exec->argument(0);
1119 JSValue a1 = exec->argument(1);
1121 // The arg processing is very much like ArrayProtoFunc::Slice
1122 double start = a0.toInteger(exec);
1123 double end = a1.isUndefined() ? len : a1.toInteger(exec);
1124 double from = start < 0 ? len + start : start;
1125 double to = end < 0 ? len + end : end;
1126 if (to > from && to > 0 && from < len) {
1131 return JSValue::encode(jsSubstring(exec, s, static_cast<unsigned>(from), static_cast<unsigned>(to) - static_cast<unsigned>(from)));
1134 return JSValue::encode(jsEmptyString(exec));
1137 // Return true in case of early return (resultLength got to limitLength).
1138 template<typename CharacterType>
1139 static ALWAYS_INLINE bool splitStringByOneCharacterImpl(ExecState* exec, JSArray* result, JSValue originalValue, const String& input, StringImpl* string, UChar separatorCharacter, size_t& position, unsigned& resultLength, unsigned limitLength)
1141 VM& vm = exec->vm();
1142 auto scope = DECLARE_THROW_SCOPE(vm);
1145 size_t matchPosition;
1146 const CharacterType* characters = string->characters<CharacterType>();
1147 // 13. Repeat, while q != s
1148 // a. Call SplitMatch(S, q, R) and let z be its MatchResult result.
1149 // b. If z is failure, then let q = q+1.
1150 // c. Else, z is not failure
1151 while ((matchPosition = WTF::find(characters, string->length(), separatorCharacter, position)) != notFound) {
1152 // 1. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1153 // through q (exclusive).
1154 // 2. Call the [[DefineOwnProperty]] internal method of A with arguments ToString(lengthA),
1155 // Property Descriptor {[[Value]]: T, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
1156 result->putDirectIndex(exec, resultLength, jsSubstring(exec, originalValue, input, position, matchPosition - position));
1157 RETURN_IF_EXCEPTION(scope, false);
1158 // 3. Increment lengthA by 1.
1159 // 4. If lengthA == lim, return A.
1160 if (++resultLength == limitLength)
1165 position = matchPosition + 1;
1170 // ES 21.1.3.17 String.prototype.split(separator, limit)
1171 EncodedJSValue JSC_HOST_CALL stringProtoFuncSplitFast(ExecState* exec)
1173 VM& vm = exec->vm();
1174 auto scope = DECLARE_THROW_SCOPE(vm);
1175 JSValue thisValue = exec->thisValue();
1176 ASSERT(checkObjectCoercible(thisValue));
1178 // 3. Let S be the result of calling ToString, giving it the this value as its argument.
1179 // 7. Let s be the number of characters in S.
1180 String input = thisValue.toWTFString(exec);
1181 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1182 ASSERT(!input.isNull());
1184 // 4. Let A be a new array created as if by the expression new Array()
1185 // where Array is the standard built-in constructor with that name.
1186 JSArray* result = constructEmptyArray(exec, 0);
1187 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1189 // 5. Let lengthA be 0.
1190 unsigned resultLength = 0;
1192 // 6. If limit is undefined, let lim = 2^32-1; else let lim = ToUint32(limit).
1193 JSValue limitValue = exec->uncheckedArgument(1);
1194 unsigned limit = limitValue.isUndefined() ? 0xFFFFFFFFu : limitValue.toUInt32(exec);
1197 size_t position = 0;
1199 // 9. If separator is a RegExp object (its [[Class]] is "RegExp"), let R = separator;
1200 // otherwise let R = ToString(separator).
1201 JSValue separatorValue = exec->uncheckedArgument(0);
1202 String separator = separatorValue.toWTFString(exec);
1203 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1205 // 10. If lim == 0, return A.
1207 return JSValue::encode(result);
1209 // 11. If separator is undefined, then
1210 if (separatorValue.isUndefined()) {
1211 // a. Call the [[DefineOwnProperty]] internal method of A with arguments "0",
1213 result->putDirectIndex(exec, 0, jsStringWithReuse(exec, thisValue, input));
1215 return JSValue::encode(result);
1218 // 12. If s == 0, then
1219 if (input.isEmpty()) {
1220 // a. Let z be SplitMatch(S, 0, R) where S is input, R is separator.
1221 // b. If z is not false, return A.
1222 // c. Call CreateDataProperty(A, "0", S).
1224 if (!separator.isEmpty()) {
1226 result->putDirectIndex(exec, 0, jsStringWithReuse(exec, thisValue, input));
1228 return JSValue::encode(result);
1231 // Optimized case for splitting on the empty string.
1232 if (separator.isEmpty()) {
1233 limit = std::min(limit, input.length());
1234 // Zero limt/input length handled in steps 9/11 respectively, above.
1238 result->putDirectIndex(exec, position, jsSingleCharacterString(exec, input[position]));
1239 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1240 } while (++position < limit);
1242 return JSValue::encode(result);
1246 // -separator length == 1, 8 bits
1247 // -separator length == 1, 16 bits
1248 // -separator length > 1
1249 StringImpl* stringImpl = input.impl();
1250 StringImpl* separatorImpl = separator.impl();
1251 size_t separatorLength = separatorImpl->length();
1253 if (separatorLength == 1) {
1254 UChar separatorCharacter;
1255 if (separatorImpl->is8Bit())
1256 separatorCharacter = separatorImpl->characters8()[0];
1258 separatorCharacter = separatorImpl->characters16()[0];
1260 if (stringImpl->is8Bit()) {
1261 if (splitStringByOneCharacterImpl<LChar>(exec, result, thisValue, input, stringImpl, separatorCharacter, position, resultLength, limit)) {
1263 return JSValue::encode(result);
1266 if (splitStringByOneCharacterImpl<UChar>(exec, result, thisValue, input, stringImpl, separatorCharacter, position, resultLength, limit)) {
1268 return JSValue::encode(result);
1271 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1274 size_t matchPosition;
1275 // 14. Repeat, while q != s
1276 // a. let e be SplitMatch(S, q, R).
1277 // b. If e is failure, then let q = q+1.
1278 // c. Else, e is an integer index <= s.
1279 while ((matchPosition = stringImpl->find(separatorImpl, position)) != notFound) {
1280 // 1. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1281 // through q (exclusive).
1282 // 2. Call CreateDataProperty(A, ToString(lengthA), T).
1283 result->putDirectIndex(exec, resultLength, jsSubstring(exec, thisValue, input, position, matchPosition - position));
1284 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1285 // 3. Increment lengthA by 1.
1286 // 4. If lengthA == lim, return A.
1287 if (++resultLength == limit)
1288 return JSValue::encode(result);
1292 position = matchPosition + separator.length();
1296 // 15. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1297 // through s (exclusive).
1298 // 16. Call CreateDataProperty(A, ToString(lengthA), T).
1300 result->putDirectIndex(exec, resultLength++, jsSubstring(exec, thisValue, input, position, input.length() - position));
1303 return JSValue::encode(result);
1306 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState* exec)
1308 VM& vm = exec->vm();
1309 auto scope = DECLARE_THROW_SCOPE(vm);
1311 JSValue thisValue = exec->thisValue();
1312 if (!checkObjectCoercible(thisValue))
1313 return throwVMTypeError(exec, scope);
1315 JSString* jsString = 0;
1317 if (thisValue.isString()) {
1318 jsString = asString(thisValue);
1319 len = jsString->length();
1321 uString = thisValue.toWTFString(exec);
1322 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1323 len = uString.length();
1326 JSValue a0 = exec->argument(0);
1327 JSValue a1 = exec->argument(1);
1329 double start = a0.toInteger(exec);
1330 double length = a1.isUndefined() ? len : a1.toInteger(exec);
1331 if (start >= len || length <= 0)
1332 return JSValue::encode(jsEmptyString(exec));
1338 if (start + length > len)
1339 length = len - start;
1340 unsigned substringStart = static_cast<unsigned>(start);
1341 unsigned substringLength = static_cast<unsigned>(length);
1343 return JSValue::encode(jsSubstring(exec, jsString, substringStart, substringLength));
1344 return JSValue::encode(jsSubstring(exec, uString, substringStart, substringLength));
1347 EncodedJSValue JSC_HOST_CALL builtinStringSubstrInternal(ExecState* exec)
1349 // @substrInternal should not have any observable side effects (e.g. it should not call
1350 // GetMethod(..., @@toPrimitive) on the thisValue).
1352 // It is ok to use the default stringProtoFuncSubstr as the implementation of
1353 // @substrInternal because @substrInternal will only be called by builtins, which will
1354 // guarantee that we only pass it a string thisValue. As a result, stringProtoFuncSubstr
1355 // will not need to call toString() on the thisValue, and there will be no observable
1357 ASSERT(exec->thisValue().isString());
1358 return stringProtoFuncSubstr(exec);
1361 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState* exec)
1363 VM& vm = exec->vm();
1364 auto scope = DECLARE_THROW_SCOPE(vm);
1366 JSValue thisValue = exec->thisValue();
1367 if (!checkObjectCoercible(thisValue))
1368 return throwVMTypeError(exec, scope);
1370 JSString* jsString = thisValue.toString(exec);
1371 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1373 JSValue a0 = exec->argument(0);
1374 JSValue a1 = exec->argument(1);
1375 int len = jsString->length();
1376 RELEASE_ASSERT(len >= 0);
1378 double start = a0.toNumber(exec);
1379 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1381 if (!(start >= 0)) // check for negative values or NaN
1383 else if (start > len)
1385 if (a1.isUndefined())
1388 end = a1.toNumber(exec);
1389 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1390 if (!(end >= 0)) // check for negative values or NaN
1400 unsigned substringStart = static_cast<unsigned>(start);
1401 unsigned substringLength = static_cast<unsigned>(end) - substringStart;
1402 return JSValue::encode(jsSubstring(exec, jsString, substringStart, substringLength));
1405 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState* exec)
1407 VM& vm = exec->vm();
1408 auto scope = DECLARE_THROW_SCOPE(vm);
1410 JSValue thisValue = exec->thisValue();
1411 if (!checkObjectCoercible(thisValue))
1412 return throwVMTypeError(exec, scope);
1413 JSString* sVal = thisValue.toString(exec);
1414 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1415 const String& s = sVal->value(exec);
1416 String lowercasedString = s.convertToLowercaseWithoutLocale();
1417 if (lowercasedString.impl() == s.impl())
1418 return JSValue::encode(sVal);
1420 return JSValue::encode(jsString(exec, lowercasedString));
1423 EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState* exec)
1425 VM& vm = exec->vm();
1426 auto scope = DECLARE_THROW_SCOPE(vm);
1428 JSValue thisValue = exec->thisValue();
1429 if (!checkObjectCoercible(thisValue))
1430 return throwVMTypeError(exec, scope);
1431 JSString* sVal = thisValue.toString(exec);
1432 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1433 const String& s = sVal->value(exec);
1434 String uppercasedString = s.convertToUppercaseWithoutLocale();
1435 if (uppercasedString.impl() == s.impl())
1436 return JSValue::encode(sVal);
1438 return JSValue::encode(jsString(exec, uppercasedString));
1441 EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState* exec)
1443 VM& vm = exec->vm();
1444 auto scope = DECLARE_THROW_SCOPE(vm);
1446 JSValue thisValue = exec->thisValue();
1447 if (!checkObjectCoercible(thisValue))
1448 return throwVMTypeError(exec, scope);
1449 String s = thisValue.toWTFString(exec);
1450 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1452 JSValue a0 = exec->argument(0);
1453 String str = a0.toWTFString(exec);
1454 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1455 return JSValue::encode(jsNumber(Collator().collate(s, str)));
1459 static EncodedJSValue toLocaleCase(ExecState* state, int32_t (*convertCase)(UChar*, int32_t, const UChar*, int32_t, const char*, UErrorCode*))
1461 VM& vm = state->vm();
1462 auto scope = DECLARE_THROW_SCOPE(vm);
1464 // 1. Let O be RequireObjectCoercible(this value).
1465 JSValue thisValue = state->thisValue();
1466 if (!checkObjectCoercible(thisValue))
1467 return throwVMTypeError(state, scope);
1469 // 2. Let S be ToString(O).
1470 JSString* sVal = thisValue.toString(state);
1471 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1472 const String& s = sVal->value(state);
1474 // 3. ReturnIfAbrupt(S).
1475 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1477 // Optimization for empty strings.
1479 return JSValue::encode(sVal);
1481 // 4. Let requestedLocales be CanonicalizeLocaleList(locales).
1482 Vector<String> requestedLocales = canonicalizeLocaleList(*state, state->argument(0));
1484 // 5. ReturnIfAbrupt(requestedLocales).
1485 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1487 // 6. Let len be the number of elements in requestedLocales.
1488 size_t len = requestedLocales.size();
1490 // 7. If len > 0, then
1491 // a. Let requestedLocale be the first element of requestedLocales.
1493 // a. Let requestedLocale be DefaultLocale().
1494 String requestedLocale = len > 0 ? requestedLocales.first() : defaultLocale(*state);
1496 // 9. Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences (6.2.1) removed.
1497 String noExtensionsLocale = removeUnicodeLocaleExtension(requestedLocale);
1499 // 10. Let availableLocales be a List with the language tags of the languages for which the Unicode character database contains language sensitive case mappings.
1500 // Note 1: As of Unicode 5.1, the availableLocales list contains the elements "az", "lt", and "tr".
1501 const HashSet<String> availableLocales({ ASCIILiteral("az"), ASCIILiteral("lt"), ASCIILiteral("tr") });
1503 // 11. Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
1504 String locale = bestAvailableLocale(availableLocales, noExtensionsLocale);
1506 // 12. If locale is undefined, let locale be "und".
1507 if (locale.isNull())
1508 locale = ASCIILiteral("und");
1510 CString utf8LocaleBuffer = locale.utf8();
1511 const StringView view(s);
1512 const int32_t viewLength = view.length();
1514 // Delegate the following steps to icu u_strToLower or u_strToUpper.
1515 // 13. Let cpList be a List containing in order the code points of S as defined in ES2015, 6.1.4, starting at the first element of S.
1516 // 14. For each code point c in cpList, if the Unicode Character Database provides a lower(/upper) case equivalent of c that is either language insensitive or for the language locale, then replace c in cpList with that/those equivalent code point(s).
1517 // 15. Let cuList be a new List.
1518 // 16. For each code point c in cpList, in order, append to cuList the elements of the UTF-16 Encoding (defined in ES2015, 6.1.4) of c.
1519 // 17. Let L be a String whose elements are, in order, the elements of cuList.
1521 // Most strings lower/upper case will be the same size as original, so try that first.
1522 UErrorCode error(U_ZERO_ERROR);
1523 Vector<UChar> buffer(viewLength);
1525 const int32_t resultLength = convertCase(buffer.data(), viewLength, view.upconvertedCharacters(), viewLength, utf8LocaleBuffer.data(), &error);
1526 if (U_SUCCESS(error))
1527 lower = String(buffer.data(), resultLength);
1528 else if (error == U_BUFFER_OVERFLOW_ERROR) {
1529 // Converted case needs more space than original. Try again.
1530 UErrorCode error(U_ZERO_ERROR);
1531 Vector<UChar> buffer(resultLength);
1532 convertCase(buffer.data(), resultLength, view.upconvertedCharacters(), viewLength, utf8LocaleBuffer.data(), &error);
1533 if (U_FAILURE(error))
1534 return throwVMTypeError(state, scope, u_errorName(error));
1535 lower = String(buffer.data(), resultLength);
1537 return throwVMTypeError(state, scope, u_errorName(error));
1541 return JSValue::encode(jsString(state, lower));
1544 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleLowerCase(ExecState* state)
1546 // 13.1.2 String.prototype.toLocaleLowerCase ([locales])
1547 // http://ecma-international.org/publications/standards/Ecma-402.htm
1548 return toLocaleCase(state, u_strToLower);
1551 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleUpperCase(ExecState* state)
1553 // 13.1.3 String.prototype.toLocaleUpperCase ([locales])
1554 // http://ecma-international.org/publications/standards/Ecma-402.htm
1555 // This function interprets a string value as a sequence of code points, as described in ES2015, 6.1.4. This function behaves in exactly the same way as String.prototype.toLocaleLowerCase, except that characters are mapped to their uppercase equivalents as specified in the Unicode character database.
1556 return toLocaleCase(state, u_strToUpper);
1558 #endif // ENABLE(INTL)
1565 static inline JSValue trimString(ExecState* exec, JSValue thisValue, int trimKind)
1567 VM& vm = exec->vm();
1568 auto scope = DECLARE_THROW_SCOPE(vm);
1570 if (!checkObjectCoercible(thisValue))
1571 return throwTypeError(exec, scope);
1572 String str = thisValue.toWTFString(exec);
1573 RETURN_IF_EXCEPTION(scope, { });
1576 if (trimKind & TrimLeft) {
1577 while (left < str.length() && isStrWhiteSpace(str[left]))
1580 unsigned right = str.length();
1581 if (trimKind & TrimRight) {
1582 while (right > left && isStrWhiteSpace(str[right - 1]))
1586 // Don't gc allocate a new string if we don't have to.
1587 if (left == 0 && right == str.length() && thisValue.isString())
1591 return jsString(exec, str.substringSharingImpl(left, right - left));
1594 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState* exec)
1596 JSValue thisValue = exec->thisValue();
1597 return JSValue::encode(trimString(exec, thisValue, TrimLeft | TrimRight));
1600 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState* exec)
1602 JSValue thisValue = exec->thisValue();
1603 return JSValue::encode(trimString(exec, thisValue, TrimLeft));
1606 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState* exec)
1608 JSValue thisValue = exec->thisValue();
1609 return JSValue::encode(trimString(exec, thisValue, TrimRight));
1612 static inline unsigned clampAndTruncateToUnsigned(double value, unsigned min, unsigned max)
1618 return static_cast<unsigned>(value);
1621 EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState* exec)
1623 VM& vm = exec->vm();
1624 auto scope = DECLARE_THROW_SCOPE(vm);
1626 JSValue thisValue = exec->thisValue();
1627 if (!checkObjectCoercible(thisValue))
1628 return throwVMTypeError(exec, scope);
1630 String stringToSearchIn = thisValue.toWTFString(exec);
1631 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1633 JSValue a0 = exec->argument(0);
1634 bool isRegularExpression = isRegExp(vm, exec, a0);
1635 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1636 if (isRegularExpression)
1637 return throwVMTypeError(exec, scope, "Argument to String.prototype.startsWith cannot be a RegExp");
1639 String searchString = a0.toWTFString(exec);
1640 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1642 JSValue positionArg = exec->argument(1);
1644 if (positionArg.isInt32())
1645 start = std::max(0, positionArg.asInt32());
1647 unsigned length = stringToSearchIn.length();
1648 start = clampAndTruncateToUnsigned(positionArg.toInteger(exec), 0, length);
1649 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1652 return JSValue::encode(jsBoolean(stringToSearchIn.hasInfixStartingAt(searchString, start)));
1655 EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState* exec)
1657 VM& vm = exec->vm();
1658 auto scope = DECLARE_THROW_SCOPE(vm);
1660 JSValue thisValue = exec->thisValue();
1661 if (!checkObjectCoercible(thisValue))
1662 return throwVMTypeError(exec, scope);
1664 String stringToSearchIn = thisValue.toWTFString(exec);
1665 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1667 JSValue a0 = exec->argument(0);
1668 bool isRegularExpression = isRegExp(vm, exec, a0);
1669 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1670 if (isRegularExpression)
1671 return throwVMTypeError(exec, scope, "Argument to String.prototype.endsWith cannot be a RegExp");
1673 String searchString = a0.toWTFString(exec);
1674 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1676 unsigned length = stringToSearchIn.length();
1678 JSValue endPositionArg = exec->argument(1);
1679 unsigned end = length;
1680 if (endPositionArg.isInt32())
1681 end = std::max(0, endPositionArg.asInt32());
1682 else if (!endPositionArg.isUndefined()) {
1683 end = clampAndTruncateToUnsigned(endPositionArg.toInteger(exec), 0, length);
1684 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1687 return JSValue::encode(jsBoolean(stringToSearchIn.hasInfixEndingAt(searchString, std::min(end, length))));
1690 static EncodedJSValue JSC_HOST_CALL stringIncludesImpl(VM& vm, ExecState* exec, String stringToSearchIn, String searchString, JSValue positionArg)
1692 auto scope = DECLARE_THROW_SCOPE(vm);
1694 if (positionArg.isInt32())
1695 start = std::max(0, positionArg.asInt32());
1697 unsigned length = stringToSearchIn.length();
1698 start = clampAndTruncateToUnsigned(positionArg.toInteger(exec), 0, length);
1699 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1702 return JSValue::encode(jsBoolean(stringToSearchIn.contains(searchString, true, start)));
1705 EncodedJSValue JSC_HOST_CALL stringProtoFuncIncludes(ExecState* exec)
1707 VM& vm = exec->vm();
1708 auto scope = DECLARE_THROW_SCOPE(vm);
1710 JSValue thisValue = exec->thisValue();
1711 if (!checkObjectCoercible(thisValue))
1712 return throwVMTypeError(exec, scope);
1714 String stringToSearchIn = thisValue.toWTFString(exec);
1715 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1717 JSValue a0 = exec->argument(0);
1718 bool isRegularExpression = isRegExp(vm, exec, a0);
1719 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1720 if (isRegularExpression)
1721 return throwVMTypeError(exec, scope, "Argument to String.prototype.includes cannot be a RegExp");
1723 String searchString = a0.toWTFString(exec);
1724 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1726 JSValue positionArg = exec->argument(1);
1729 return stringIncludesImpl(vm, exec, stringToSearchIn, searchString, positionArg);
1732 EncodedJSValue JSC_HOST_CALL builtinStringIncludesInternal(ExecState* exec)
1734 VM& vm = exec->vm();
1735 auto scope = DECLARE_THROW_SCOPE(vm);
1737 JSValue thisValue = exec->thisValue();
1738 ASSERT(checkObjectCoercible(thisValue));
1740 String stringToSearchIn = thisValue.toWTFString(exec);
1741 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1743 JSValue a0 = exec->uncheckedArgument(0);
1744 String searchString = a0.toWTFString(exec);
1745 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1747 JSValue positionArg = exec->argument(1);
1750 return stringIncludesImpl(vm, exec, stringToSearchIn, searchString, positionArg);
1753 EncodedJSValue JSC_HOST_CALL stringProtoFuncIterator(ExecState* exec)
1755 VM& vm = exec->vm();
1756 auto scope = DECLARE_THROW_SCOPE(vm);
1758 JSValue thisValue = exec->thisValue();
1759 if (!checkObjectCoercible(thisValue))
1760 return throwVMTypeError(exec, scope);
1761 JSString* string = thisValue.toString(exec);
1762 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1763 return JSValue::encode(JSStringIterator::create(exec, exec->jsCallee()->globalObject()->stringIteratorStructure(), string));
1766 static JSValue normalize(ExecState* exec, const UChar* source, size_t sourceLength, UNormalizationMode form)
1768 VM& vm = exec->vm();
1769 auto scope = DECLARE_THROW_SCOPE(vm);
1771 UErrorCode status = U_ZERO_ERROR;
1772 int32_t normalizedStringLength = unorm_normalize(source, sourceLength, form, 0, nullptr, 0, &status);
1774 if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
1775 // The behavior is not specified when normalize fails.
1776 // Now we throw a type erorr since it seems that the contents of the string are invalid.
1777 return throwTypeError(exec, scope);
1780 UChar* buffer = nullptr;
1781 auto impl = StringImpl::tryCreateUninitialized(normalizedStringLength, buffer);
1783 return throwOutOfMemoryError(exec, scope);
1785 status = U_ZERO_ERROR;
1786 unorm_normalize(source, sourceLength, form, 0, buffer, normalizedStringLength, &status);
1787 if (U_FAILURE(status))
1788 return throwTypeError(exec, scope);
1791 return jsString(exec, WTFMove(impl));
1794 EncodedJSValue JSC_HOST_CALL stringProtoFuncNormalize(ExecState* exec)
1796 VM& vm = exec->vm();
1797 auto scope = DECLARE_THROW_SCOPE(vm);
1799 JSValue thisValue = exec->thisValue();
1800 if (!checkObjectCoercible(thisValue))
1801 return throwVMTypeError(exec, scope);
1802 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
1803 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1804 StringView view = viewWithString.view;
1806 UNormalizationMode form = UNORM_NFC;
1807 // Verify that the argument is provided and is not undefined.
1808 if (!exec->argument(0).isUndefined()) {
1809 String formString = exec->uncheckedArgument(0).toWTFString(exec);
1810 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1812 if (formString == "NFC")
1814 else if (formString == "NFD")
1816 else if (formString == "NFKC")
1818 else if (formString == "NFKD")
1821 return throwVMError(exec, scope, createRangeError(exec, ASCIILiteral("argument does not match any normalization form")));
1825 return JSValue::encode(normalize(exec, view.upconvertedCharacters(), view.length(), form));