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 stringProtoFuncIndexOf(ExecState*);
67 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*);
68 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState*);
69 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState*);
70 EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState*);
71 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState*);
72 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState*);
73 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState*);
74 EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState*);
75 EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState*);
76 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleLowerCase(ExecState*);
77 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleUpperCase(ExecState*);
78 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState*);
79 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState*);
80 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState*);
81 EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState*);
82 EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState*);
83 EncodedJSValue JSC_HOST_CALL stringProtoFuncIncludes(ExecState*);
84 EncodedJSValue JSC_HOST_CALL stringProtoFuncNormalize(ExecState*);
85 EncodedJSValue JSC_HOST_CALL stringProtoFuncIterator(ExecState*);
89 #include "StringPrototype.lut.h"
93 const ClassInfo StringPrototype::s_info = { "String", &StringObject::s_info, &stringPrototypeTable, nullptr, CREATE_METHOD_TABLE(StringPrototype) };
95 /* Source for StringConstructor.lut.h
96 @begin stringPrototypeTable
97 concat JSBuiltin DontEnum|Function 1
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("indexOf", stringProtoFuncIndexOf, DontEnum, 1);
138 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", stringProtoFuncLastIndexOf, DontEnum, 1);
139 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingRegExpPrivateName(), stringProtoFuncReplaceUsingRegExp, DontEnum, 2, StringPrototypeReplaceRegExpIntrinsic);
140 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingStringSearchPrivateName(), stringProtoFuncReplaceUsingStringSearch, DontEnum, 2);
141 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, DontEnum, 2);
142 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substr", stringProtoFuncSubstr, DontEnum, 2);
143 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substring", stringProtoFuncSubstring, DontEnum, 2);
144 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0, StringPrototypeToLowerCaseIntrinsic);
145 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
147 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringPrototypeLocaleCompareCodeGenerator, DontEnum);
148 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleLowerCase", stringProtoFuncToLocaleLowerCase, DontEnum, 0);
149 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleUpperCase", stringProtoFuncToLocaleUpperCase, DontEnum, 0);
151 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringProtoFuncLocaleCompare, DontEnum, 1);
152 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleLowerCase", stringProtoFuncToLowerCase, DontEnum, 0);
153 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
155 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trim", stringProtoFuncTrim, DontEnum, 0);
156 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trimLeft", stringProtoFuncTrimLeft, DontEnum, 0);
157 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trimRight", stringProtoFuncTrimRight, DontEnum, 0);
158 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("startsWith", stringProtoFuncStartsWith, DontEnum, 1);
159 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("endsWith", stringProtoFuncEndsWith, DontEnum, 1);
160 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("includes", stringProtoFuncIncludes, DontEnum, 1);
161 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("normalize", stringProtoFuncNormalize, DontEnum, 0);
162 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().charCodeAtPrivateName(), stringProtoFuncCharCodeAt, DontEnum, 1, CharCodeAtIntrinsic);
164 JSFunction* iteratorFunction = JSFunction::create(vm, globalObject, 0, ASCIILiteral("[Symbol.iterator]"), stringProtoFuncIterator, NoIntrinsic);
165 putDirectWithoutTransition(vm, vm.propertyNames->iteratorSymbol, iteratorFunction, DontEnum);
167 // The constructor will be added later, after StringConstructor has been built
168 putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
171 StringPrototype* StringPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
173 JSString* empty = jsEmptyString(&vm);
174 StringPrototype* prototype = new (NotNull, allocateCell<StringPrototype>(vm.heap)) StringPrototype(vm, structure);
175 prototype->finishCreation(vm, globalObject, empty);
179 // ------------------------------ Functions --------------------------
181 static NEVER_INLINE String substituteBackreferencesSlow(StringView replacement, StringView source, const int* ovector, RegExp* reg, size_t i)
183 StringBuilder substitutedReplacement;
186 if (i + 1 == replacement.length())
189 UChar ref = replacement[i + 1];
193 substitutedReplacement.append(replacement.substring(offset, i - offset));
202 backrefStart = ovector[0];
203 backrefLength = ovector[1] - backrefStart;
204 } else if (ref == '`') {
206 backrefLength = ovector[0];
207 } else if (ref == '\'') {
208 backrefStart = ovector[1];
209 backrefLength = source.length() - backrefStart;
210 } else if (reg && isASCIIDigit(ref)) {
211 // 1- and 2-digit back references are allowed
212 unsigned backrefIndex = ref - '0';
213 if (backrefIndex > reg->numSubpatterns())
215 if (replacement.length() > i + 2) {
216 ref = replacement[i + 2];
217 if (isASCIIDigit(ref)) {
218 backrefIndex = 10 * backrefIndex + ref - '0';
219 if (backrefIndex > reg->numSubpatterns())
220 backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference
227 backrefStart = ovector[2 * backrefIndex];
228 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
233 substitutedReplacement.append(replacement.substring(offset, i - offset));
236 if (backrefStart >= 0)
237 substitutedReplacement.append(source.substring(backrefStart, backrefLength));
238 } while ((i = replacement.find('$', i + 1)) != notFound);
240 if (replacement.length() - offset)
241 substitutedReplacement.append(replacement.substring(offset));
243 return substitutedReplacement.toString();
246 inline String substituteBackreferencesInline(const String& replacement, StringView source, const int* ovector, RegExp* reg)
248 size_t i = replacement.find('$');
249 if (UNLIKELY(i != notFound))
250 return substituteBackreferencesSlow(replacement, source, ovector, reg, i);
255 String substituteBackreferences(const String& replacement, StringView source, const int* ovector, RegExp* reg)
257 return substituteBackreferencesInline(replacement, source, ovector, reg);
261 StringRange(int pos, int len)
275 static ALWAYS_INLINE JSValue jsSpliceSubstrings(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount)
278 auto scope = DECLARE_THROW_SCOPE(vm);
280 if (rangeCount == 1) {
281 int sourceSize = source.length();
282 int position = substringRanges[0].position;
283 int length = substringRanges[0].length;
284 if (position <= 0 && length >= sourceSize)
286 // We could call String::substringSharingImpl(), but this would result in redundant checks.
288 return jsString(exec, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length)));
292 for (int i = 0; i < rangeCount; i++)
293 totalLength += substringRanges[i].length;
296 return jsEmptyString(exec);
298 if (source.is8Bit()) {
300 const LChar* sourceData = source.characters8();
301 auto impl = StringImpl::tryCreateUninitialized(totalLength, buffer);
303 return throwOutOfMemoryError(exec, scope);
306 for (int i = 0; i < rangeCount; i++) {
307 if (int srcLen = substringRanges[i].length) {
308 StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
314 return jsString(exec, WTFMove(impl));
318 const UChar* sourceData = source.characters16();
320 auto impl = StringImpl::tryCreateUninitialized(totalLength, buffer);
322 return throwOutOfMemoryError(exec, scope);
325 for (int i = 0; i < rangeCount; i++) {
326 if (int srcLen = substringRanges[i].length) {
327 StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
333 return jsString(exec, WTFMove(impl));
336 static ALWAYS_INLINE JSValue jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount, const String* separators, int separatorCount)
339 auto scope = DECLARE_THROW_SCOPE(vm);
341 if (rangeCount == 1 && separatorCount == 0) {
342 int sourceSize = source.length();
343 int position = substringRanges[0].position;
344 int length = substringRanges[0].length;
345 if (position <= 0 && length >= sourceSize)
347 // We could call String::substringSharingImpl(), but this would result in redundant checks.
349 return jsString(exec, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length)));
352 Checked<int, RecordOverflow> totalLength = 0;
353 bool allSeparators8Bit = true;
354 for (int i = 0; i < rangeCount; i++)
355 totalLength += substringRanges[i].length;
356 for (int i = 0; i < separatorCount; i++) {
357 totalLength += separators[i].length();
358 if (separators[i].length() && !separators[i].is8Bit())
359 allSeparators8Bit = false;
361 if (totalLength.hasOverflowed())
362 return throwOutOfMemoryError(exec, scope);
365 return jsEmptyString(exec);
367 if (source.is8Bit() && allSeparators8Bit) {
369 const LChar* sourceData = source.characters8();
371 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
373 return throwOutOfMemoryError(exec, scope);
375 int maxCount = std::max(rangeCount, separatorCount);
377 for (int i = 0; i < maxCount; i++) {
378 if (i < rangeCount) {
379 if (int srcLen = substringRanges[i].length) {
380 StringImpl::copyChars(buffer + bufferPos, sourceData + substringRanges[i].position, srcLen);
384 if (i < separatorCount) {
385 if (int sepLen = separators[i].length()) {
386 StringImpl::copyChars(buffer + bufferPos, separators[i].characters8(), sepLen);
393 return jsString(exec, WTFMove(impl));
397 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
399 return throwOutOfMemoryError(exec, scope);
401 int maxCount = std::max(rangeCount, separatorCount);
403 for (int i = 0; i < maxCount; i++) {
404 if (i < rangeCount) {
405 if (int srcLen = substringRanges[i].length) {
407 StringImpl::copyChars(buffer + bufferPos, source.characters8() + substringRanges[i].position, srcLen);
409 StringImpl::copyChars(buffer + bufferPos, source.characters16() + substringRanges[i].position, srcLen);
413 if (i < separatorCount) {
414 if (int sepLen = separators[i].length()) {
415 if (separators[i].is8Bit())
416 StringImpl::copyChars(buffer + bufferPos, separators[i].characters8(), sepLen);
418 StringImpl::copyChars(buffer + bufferPos, separators[i].characters16(), sepLen);
425 return jsString(exec, WTFMove(impl));
428 #define OUT_OF_MEMORY(exec__, scope__) \
430 throwOutOfMemoryError(exec__, scope__); \
431 return encodedJSValue(); \
434 static ALWAYS_INLINE EncodedJSValue removeUsingRegExpSearch(VM& vm, ExecState* exec, JSString* string, const String& source, RegExp* regExp)
436 auto scope = DECLARE_THROW_SCOPE(vm);
437 SuperSamplerScope superSamplerScope(false);
439 size_t lastIndex = 0;
440 unsigned startPosition = 0;
442 Vector<StringRange, 16> sourceRanges;
443 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
444 unsigned sourceLen = source.length();
447 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition);
451 if (lastIndex < result.start) {
452 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
453 OUT_OF_MEMORY(exec, scope);
455 lastIndex = result.end;
456 startPosition = lastIndex;
458 // special case of empty match
459 if (result.empty()) {
461 if (startPosition > sourceLen)
467 return JSValue::encode(string);
469 if (static_cast<unsigned>(lastIndex) < sourceLen) {
470 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, sourceLen - lastIndex)))
471 OUT_OF_MEMORY(exec, scope);
474 return JSValue::encode(jsSpliceSubstrings(exec, string, source, sourceRanges.data(), sourceRanges.size()));
477 static ALWAYS_INLINE EncodedJSValue replaceUsingRegExpSearch(
478 VM& vm, ExecState* exec, JSString* string, JSValue searchValue, CallData& callData,
479 CallType callType, String& replacementString, JSValue replaceValue)
481 auto scope = DECLARE_THROW_SCOPE(vm);
483 const String& source = string->value(exec);
484 unsigned sourceLen = source.length();
485 RETURN_IF_EXCEPTION(scope, encodedJSValue());
486 RegExpObject* regExpObject = asRegExpObject(searchValue);
487 RegExp* regExp = regExpObject->regExp();
488 bool global = regExp->global();
491 // ES5.1 15.5.4.10 step 8.a.
492 regExpObject->setLastIndex(exec, 0);
493 RETURN_IF_EXCEPTION(scope, encodedJSValue());
495 if (callType == CallType::None && !replacementString.length()) {
497 return removeUsingRegExpSearch(vm, exec, string, source, regExp);
501 // FIXME: This is wrong because we may be called directly from the FTL.
502 // https://bugs.webkit.org/show_bug.cgi?id=154874
503 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
505 size_t lastIndex = 0;
506 unsigned startPosition = 0;
508 Vector<StringRange, 16> sourceRanges;
509 Vector<String, 16> replacements;
511 // This is either a loop (if global is set) or a one-way (if not).
512 if (global && callType == CallType::JS) {
513 // regExp->numSubpatterns() + 1 for pattern args, + 2 for match start and string
514 int argCount = regExp->numSubpatterns() + 1 + 2;
515 JSFunction* func = jsCast<JSFunction*>(replaceValue);
516 CachedCall cachedCall(exec, func, argCount);
517 RETURN_IF_EXCEPTION(scope, encodedJSValue());
518 if (source.is8Bit()) {
521 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition, &ovector);
525 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
526 OUT_OF_MEMORY(exec, scope);
529 cachedCall.clearArguments();
530 for (; i < regExp->numSubpatterns() + 1; ++i) {
531 int matchStart = ovector[i * 2];
532 int matchLen = ovector[i * 2 + 1] - matchStart;
535 cachedCall.appendArgument(jsUndefined());
537 cachedCall.appendArgument(jsSubstring(&vm, source, matchStart, matchLen));
540 cachedCall.appendArgument(jsNumber(result.start));
541 cachedCall.appendArgument(string);
543 cachedCall.setThis(jsUndefined());
544 JSValue jsResult = cachedCall.call();
545 RETURN_IF_EXCEPTION(scope, encodedJSValue());
546 replacements.append(jsResult.toWTFString(exec));
547 RETURN_IF_EXCEPTION(scope, encodedJSValue());
549 lastIndex = result.end;
550 startPosition = lastIndex;
552 // special case of empty match
553 if (result.empty()) {
555 if (startPosition > sourceLen)
562 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition, &ovector);
566 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
567 OUT_OF_MEMORY(exec, scope);
570 cachedCall.clearArguments();
571 for (; i < regExp->numSubpatterns() + 1; ++i) {
572 int matchStart = ovector[i * 2];
573 int matchLen = ovector[i * 2 + 1] - matchStart;
576 cachedCall.appendArgument(jsUndefined());
578 cachedCall.appendArgument(jsSubstring(&vm, source, matchStart, matchLen));
581 cachedCall.appendArgument(jsNumber(result.start));
582 cachedCall.appendArgument(string);
584 cachedCall.setThis(jsUndefined());
585 JSValue jsResult = cachedCall.call();
586 RETURN_IF_EXCEPTION(scope, encodedJSValue());
587 replacements.append(jsResult.toWTFString(exec));
588 RETURN_IF_EXCEPTION(scope, encodedJSValue());
590 lastIndex = result.end;
591 startPosition = lastIndex;
593 // special case of empty match
594 if (result.empty()) {
596 if (startPosition > sourceLen)
604 MatchResult result = regExpConstructor->performMatch(vm, regExp, string, source, startPosition, &ovector);
608 if (callType != CallType::None) {
609 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
610 OUT_OF_MEMORY(exec, scope);
612 MarkedArgumentBuffer args;
614 for (unsigned i = 0; i < regExp->numSubpatterns() + 1; ++i) {
615 int matchStart = ovector[i * 2];
616 int matchLen = ovector[i * 2 + 1] - matchStart;
619 args.append(jsUndefined());
621 args.append(jsSubstring(exec, source, matchStart, matchLen));
624 args.append(jsNumber(result.start));
627 JSValue replacement = call(exec, replaceValue, callType, callData, jsUndefined(), args);
628 RETURN_IF_EXCEPTION(scope, encodedJSValue());
629 String replacementString = replacement.toWTFString(exec);
630 RETURN_IF_EXCEPTION(scope, encodedJSValue());
631 replacements.append(replacementString);
632 RETURN_IF_EXCEPTION(scope, encodedJSValue());
634 int replLen = replacementString.length();
635 if (lastIndex < result.start || replLen) {
636 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
637 OUT_OF_MEMORY(exec, scope);
640 replacements.append(substituteBackreferences(replacementString, source, ovector, regExp));
642 replacements.append(String());
646 lastIndex = result.end;
647 startPosition = lastIndex;
649 // special case of empty match
650 if (result.empty()) {
652 if (startPosition > sourceLen)
658 if (!lastIndex && replacements.isEmpty())
659 return JSValue::encode(string);
661 if (static_cast<unsigned>(lastIndex) < sourceLen) {
662 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, sourceLen - lastIndex)))
663 OUT_OF_MEMORY(exec, scope);
666 return JSValue::encode(jsSpliceSubstringsWithSeparators(exec, string, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
669 EncodedJSValue JIT_OPERATION operationStringProtoFuncReplaceRegExpEmptyStr(
670 ExecState* exec, JSString* thisValue, RegExpObject* searchValue)
673 NativeCallFrameTracer tracer(&vm, exec);
674 auto scope = DECLARE_THROW_SCOPE(vm);
676 RegExp* regExp = searchValue->regExp();
677 if (regExp->global()) {
678 // ES5.1 15.5.4.10 step 8.a.
679 searchValue->setLastIndex(exec, 0);
680 RETURN_IF_EXCEPTION(scope, encodedJSValue());
682 return removeUsingRegExpSearch(vm, exec, thisValue, thisValue->value(exec), regExp);
686 String replacementString = emptyString();
688 return replaceUsingRegExpSearch(
689 vm, exec, thisValue, searchValue, callData, CallType::None, replacementString, JSValue());
692 EncodedJSValue JIT_OPERATION operationStringProtoFuncReplaceRegExpString(
693 ExecState* exec, JSString* thisValue, RegExpObject* searchValue, JSString* replaceString)
696 NativeCallFrameTracer tracer(&vm, exec);
699 String replacementString = replaceString->value(exec);
700 return replaceUsingRegExpSearch(
701 vm, exec, thisValue, searchValue, callData, CallType::None, replacementString, replaceString);
704 static ALWAYS_INLINE EncodedJSValue replaceUsingRegExpSearch(VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
706 auto scope = DECLARE_THROW_SCOPE(vm);
708 String replacementString;
710 CallType callType = getCallData(replaceValue, callData);
711 if (callType == CallType::None) {
712 replacementString = replaceValue.toWTFString(exec);
713 RETURN_IF_EXCEPTION(scope, encodedJSValue());
717 return replaceUsingRegExpSearch(
718 vm, exec, string, searchValue, callData, callType, replacementString, replaceValue);
721 static ALWAYS_INLINE EncodedJSValue replaceUsingStringSearch(VM& vm, ExecState* exec, JSString* jsString, JSValue searchValue, JSValue replaceValue)
723 auto scope = DECLARE_THROW_SCOPE(vm);
725 const String& string = jsString->value(exec);
726 RETURN_IF_EXCEPTION(scope, encodedJSValue());
727 String searchString = searchValue.toWTFString(exec);
728 RETURN_IF_EXCEPTION(scope, encodedJSValue());
730 size_t matchStart = string.find(searchString);
732 if (matchStart == notFound)
733 return JSValue::encode(jsString);
736 CallType callType = getCallData(replaceValue, callData);
737 if (callType != CallType::None) {
738 MarkedArgumentBuffer args;
739 args.append(jsSubstring(exec, string, matchStart, searchString.impl()->length()));
740 args.append(jsNumber(matchStart));
741 args.append(jsString);
742 replaceValue = call(exec, replaceValue, callType, callData, jsUndefined(), args);
743 RETURN_IF_EXCEPTION(scope, encodedJSValue());
746 String replaceString = replaceValue.toWTFString(exec);
747 RETURN_IF_EXCEPTION(scope, encodedJSValue());
749 StringImpl* stringImpl = string.impl();
750 String leftPart(StringImpl::createSubstringSharingImpl(*stringImpl, 0, matchStart));
752 size_t matchEnd = matchStart + searchString.impl()->length();
753 int ovector[2] = { static_cast<int>(matchStart), static_cast<int>(matchEnd)};
754 String middlePart = callType != CallType::None ? replaceString : substituteBackreferences(replaceString, string, ovector, 0);
756 size_t leftLength = stringImpl->length() - matchEnd;
757 String rightPart(StringImpl::createSubstringSharingImpl(*stringImpl, matchEnd, leftLength));
759 return JSValue::encode(JSC::jsString(exec, leftPart, middlePart, rightPart));
762 static inline bool checkObjectCoercible(JSValue thisValue)
764 if (thisValue.isString())
767 if (thisValue.isUndefinedOrNull())
770 if (thisValue.isObject() && asObject(thisValue)->isEnvironmentRecord())
776 template <typename CharacterType>
777 static inline JSString* repeatCharacter(ExecState& exec, CharacterType character, unsigned repeatCount)
780 auto scope = DECLARE_THROW_SCOPE(vm);
782 CharacterType* buffer = nullptr;
783 auto impl = StringImpl::tryCreateUninitialized(repeatCount, buffer);
785 throwOutOfMemoryError(&exec, scope);
789 std::fill_n(buffer, repeatCount, character);
792 return jsString(&exec, WTFMove(impl));
795 EncodedJSValue JSC_HOST_CALL stringProtoFuncRepeatCharacter(ExecState* exec)
798 auto scope = DECLARE_THROW_SCOPE(vm);
800 // For a string which length is single, instead of creating ropes,
801 // allocating a sequential buffer and fill with the repeated string for efficiency.
802 ASSERT(exec->argumentCount() == 2);
804 ASSERT(exec->uncheckedArgument(0).isString());
805 JSString* string = asString(exec->uncheckedArgument(0));
806 ASSERT(string->length() == 1);
808 JSValue repeatCountValue = exec->uncheckedArgument(1);
809 RELEASE_ASSERT(repeatCountValue.isNumber());
811 double value = repeatCountValue.asNumber();
812 if (value > JSString::MaxLength)
813 return JSValue::encode(throwOutOfMemoryError(exec, scope));
814 repeatCount = static_cast<int32_t>(value);
815 ASSERT(repeatCount >= 0);
816 ASSERT(!repeatCountValue.isDouble() || repeatCountValue.asDouble() == repeatCount);
818 auto viewWithString = string->viewWithUnderlyingString(exec);
819 StringView view = viewWithString.view;
820 ASSERT(view.length() == 1);
821 scope.assertNoException();
822 UChar character = view[0];
824 if (!(character & ~0xff))
825 return JSValue::encode(repeatCharacter(*exec, static_cast<LChar>(character), repeatCount));
826 return JSValue::encode(repeatCharacter(*exec, character, repeatCount));
829 ALWAYS_INLINE EncodedJSValue replace(
830 VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
832 if (searchValue.inherits(vm, RegExpObject::info()))
833 return replaceUsingRegExpSearch(vm, exec, string, searchValue, replaceValue);
834 return replaceUsingStringSearch(vm, exec, string, searchValue, replaceValue);
837 ALWAYS_INLINE EncodedJSValue replace(
838 VM& vm, ExecState* exec, JSValue thisValue, JSValue searchValue, JSValue replaceValue)
840 auto scope = DECLARE_THROW_SCOPE(vm);
842 if (!checkObjectCoercible(thisValue))
843 return throwVMTypeError(exec, scope);
844 JSString* string = thisValue.toString(exec);
845 RETURN_IF_EXCEPTION(scope, encodedJSValue());
847 return replace(vm, exec, string, searchValue, replaceValue);
850 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState* exec)
853 auto scope = DECLARE_THROW_SCOPE(vm);
855 JSString* string = exec->thisValue().toString(exec);
856 RETURN_IF_EXCEPTION(scope, encodedJSValue());
858 JSValue searchValue = exec->argument(0);
859 if (!searchValue.inherits(vm, RegExpObject::info()))
860 return JSValue::encode(jsUndefined());
863 return replaceUsingRegExpSearch(vm, exec, string, searchValue, exec->argument(1));
866 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState* exec)
869 auto scope = DECLARE_THROW_SCOPE(vm);
871 JSString* string = exec->thisValue().toString(exec);
872 RETURN_IF_EXCEPTION(scope, encodedJSValue());
875 return replaceUsingStringSearch(vm, exec, string, exec->argument(0), exec->argument(1));
878 EncodedJSValue JIT_OPERATION operationStringProtoFuncReplaceGeneric(
879 ExecState* exec, EncodedJSValue thisValue, EncodedJSValue searchValue,
880 EncodedJSValue replaceValue)
883 NativeCallFrameTracer tracer(&vm, exec);
886 vm, exec, JSValue::decode(thisValue), JSValue::decode(searchValue),
887 JSValue::decode(replaceValue));
890 EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec)
893 auto scope = DECLARE_THROW_SCOPE(vm);
895 JSValue thisValue = exec->thisValue();
896 // Also used for valueOf.
898 if (thisValue.isString())
899 return JSValue::encode(thisValue);
901 if (thisValue.inherits(vm, StringObject::info()))
902 return JSValue::encode(asStringObject(thisValue)->internalValue());
904 return throwVMTypeError(exec, scope);
907 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState* exec)
910 auto scope = DECLARE_THROW_SCOPE(vm);
912 JSValue thisValue = exec->thisValue();
913 if (!checkObjectCoercible(thisValue))
914 return throwVMTypeError(exec, scope);
915 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
916 RETURN_IF_EXCEPTION(scope, encodedJSValue());
917 StringView view = viewWithString.view;
918 RETURN_IF_EXCEPTION(scope, encodedJSValue());
919 JSValue a0 = exec->argument(0);
921 uint32_t i = a0.asUInt32();
922 if (i < view.length())
923 return JSValue::encode(jsSingleCharacterString(exec, view[i]));
924 return JSValue::encode(jsEmptyString(exec));
926 double dpos = a0.toInteger(exec);
927 if (dpos >= 0 && dpos < view.length())
928 return JSValue::encode(jsSingleCharacterString(exec, view[static_cast<unsigned>(dpos)]));
929 return JSValue::encode(jsEmptyString(exec));
932 EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState* exec)
935 auto scope = DECLARE_THROW_SCOPE(vm);
937 JSValue thisValue = exec->thisValue();
938 if (!checkObjectCoercible(thisValue))
939 return throwVMTypeError(exec, scope);
940 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
941 RETURN_IF_EXCEPTION(scope, encodedJSValue());
942 StringView view = viewWithString.view;
943 JSValue a0 = exec->argument(0);
945 uint32_t i = a0.asUInt32();
946 if (i < view.length())
947 return JSValue::encode(jsNumber(view[i]));
948 return JSValue::encode(jsNaN());
950 double dpos = a0.toInteger(exec);
951 if (dpos >= 0 && dpos < view.length())
952 return JSValue::encode(jsNumber(view[static_cast<int>(dpos)]));
953 return JSValue::encode(jsNaN());
956 static inline UChar32 codePointAt(const String& string, unsigned position, unsigned length)
958 RELEASE_ASSERT(position < length);
960 return string.characters8()[position];
962 U16_NEXT(string.characters16(), position, length, character);
966 EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState* exec)
969 auto scope = DECLARE_THROW_SCOPE(vm);
971 JSValue thisValue = exec->thisValue();
972 if (!checkObjectCoercible(thisValue))
973 return throwVMTypeError(exec, scope);
975 String string = thisValue.toWTFString(exec);
976 RETURN_IF_EXCEPTION(scope, encodedJSValue());
977 unsigned length = string.length();
979 JSValue argument0 = exec->argument(0);
980 if (argument0.isUInt32()) {
981 unsigned position = argument0.asUInt32();
982 if (position < length)
983 return JSValue::encode(jsNumber(codePointAt(string, position, length)));
984 return JSValue::encode(jsUndefined());
987 RETURN_IF_EXCEPTION(scope, encodedJSValue());
989 double doublePosition = argument0.toInteger(exec);
990 RETURN_IF_EXCEPTION(scope, encodedJSValue());
991 if (doublePosition >= 0 && doublePosition < length)
992 return JSValue::encode(jsNumber(codePointAt(string, static_cast<unsigned>(doublePosition), length)));
993 return JSValue::encode(jsUndefined());
996 EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState* exec)
999 auto scope = DECLARE_THROW_SCOPE(vm);
1001 JSValue thisValue = exec->thisValue();
1002 if (!checkObjectCoercible(thisValue))
1003 return throwVMTypeError(exec, scope);
1005 JSValue a0 = exec->argument(0);
1006 JSValue a1 = exec->argument(1);
1008 JSString* thisJSString = thisValue.toString(exec);
1009 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1010 JSString* otherJSString = a0.toString(exec);
1011 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1014 if (!a1.isUndefined()) {
1015 int len = thisJSString->length();
1016 RELEASE_ASSERT(len >= 0);
1018 pos = std::min<uint32_t>(a1.asUInt32(), len);
1020 double dpos = a1.toInteger(exec);
1023 else if (dpos > len)
1025 pos = static_cast<unsigned>(dpos);
1029 if (thisJSString->length() < otherJSString->length() + pos)
1030 return JSValue::encode(jsNumber(-1));
1032 auto thisViewWithString = thisJSString->viewWithUnderlyingString(exec);
1033 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1034 auto otherViewWithString = otherJSString->viewWithUnderlyingString(exec);
1035 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1036 size_t result = thisViewWithString.view.find(otherViewWithString.view, pos);
1037 if (result == notFound)
1038 return JSValue::encode(jsNumber(-1));
1039 return JSValue::encode(jsNumber(result));
1042 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState* exec)
1044 VM& vm = exec->vm();
1045 auto scope = DECLARE_THROW_SCOPE(vm);
1047 JSValue thisValue = exec->thisValue();
1048 if (!checkObjectCoercible(thisValue))
1049 return throwVMTypeError(exec, scope);
1051 JSValue a0 = exec->argument(0);
1052 JSValue a1 = exec->argument(1);
1054 JSString* thisJSString = thisValue.toString(exec);
1055 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1056 unsigned len = thisJSString->length();
1057 JSString* otherJSString = a0.toString(exec);
1058 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1060 double dpos = a1.toIntegerPreserveNaN(exec);
1061 unsigned startPosition;
1064 else if (!(dpos <= len)) // true for NaN
1065 startPosition = len;
1067 startPosition = static_cast<unsigned>(dpos);
1069 if (len < otherJSString->length())
1070 return JSValue::encode(jsNumber(-1));
1072 String thisString = thisJSString->value(exec);
1073 String otherString = otherJSString->value(exec);
1076 result = thisString.startsWith(otherString) ? 0 : notFound;
1078 result = thisString.reverseFind(otherString, startPosition);
1079 if (result == notFound)
1080 return JSValue::encode(jsNumber(-1));
1081 return JSValue::encode(jsNumber(result));
1084 EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState* exec)
1086 VM& vm = exec->vm();
1087 auto scope = DECLARE_THROW_SCOPE(vm);
1089 JSValue thisValue = exec->thisValue();
1090 if (!checkObjectCoercible(thisValue))
1091 return throwVMTypeError(exec, scope);
1092 String s = thisValue.toWTFString(exec);
1093 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1095 int len = s.length();
1096 RELEASE_ASSERT(len >= 0);
1098 JSValue a0 = exec->argument(0);
1099 JSValue a1 = exec->argument(1);
1101 // The arg processing is very much like ArrayProtoFunc::Slice
1102 double start = a0.toInteger(exec);
1103 double end = a1.isUndefined() ? len : a1.toInteger(exec);
1104 double from = start < 0 ? len + start : start;
1105 double to = end < 0 ? len + end : end;
1106 if (to > from && to > 0 && from < len) {
1111 return JSValue::encode(jsSubstring(exec, s, static_cast<unsigned>(from), static_cast<unsigned>(to) - static_cast<unsigned>(from)));
1114 return JSValue::encode(jsEmptyString(exec));
1117 // Return true in case of early return (resultLength got to limitLength).
1118 template<typename CharacterType>
1119 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)
1121 VM& vm = exec->vm();
1122 auto scope = DECLARE_THROW_SCOPE(vm);
1125 size_t matchPosition;
1126 const CharacterType* characters = string->characters<CharacterType>();
1127 // 13. Repeat, while q != s
1128 // a. Call SplitMatch(S, q, R) and let z be its MatchResult result.
1129 // b. If z is failure, then let q = q+1.
1130 // c. Else, z is not failure
1131 while ((matchPosition = WTF::find(characters, string->length(), separatorCharacter, position)) != notFound) {
1132 // 1. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1133 // through q (exclusive).
1134 // 2. Call the [[DefineOwnProperty]] internal method of A with arguments ToString(lengthA),
1135 // Property Descriptor {[[Value]]: T, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
1136 result->putDirectIndex(exec, resultLength, jsSubstring(exec, originalValue, input, position, matchPosition - position));
1137 RETURN_IF_EXCEPTION(scope, false);
1138 // 3. Increment lengthA by 1.
1139 // 4. If lengthA == lim, return A.
1140 if (++resultLength == limitLength)
1145 position = matchPosition + 1;
1150 // ES 21.1.3.17 String.prototype.split(separator, limit)
1151 EncodedJSValue JSC_HOST_CALL stringProtoFuncSplitFast(ExecState* exec)
1153 VM& vm = exec->vm();
1154 auto scope = DECLARE_THROW_SCOPE(vm);
1155 JSValue thisValue = exec->thisValue();
1156 ASSERT(checkObjectCoercible(thisValue));
1158 // 3. Let S be the result of calling ToString, giving it the this value as its argument.
1159 // 7. Let s be the number of characters in S.
1160 String input = thisValue.toWTFString(exec);
1161 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1162 ASSERT(!input.isNull());
1164 // 4. Let A be a new array created as if by the expression new Array()
1165 // where Array is the standard built-in constructor with that name.
1166 JSArray* result = constructEmptyArray(exec, 0);
1167 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1169 // 5. Let lengthA be 0.
1170 unsigned resultLength = 0;
1172 // 6. If limit is undefined, let lim = 2^32-1; else let lim = ToUint32(limit).
1173 JSValue limitValue = exec->uncheckedArgument(1);
1174 unsigned limit = limitValue.isUndefined() ? 0xFFFFFFFFu : limitValue.toUInt32(exec);
1177 size_t position = 0;
1179 // 9. If separator is a RegExp object (its [[Class]] is "RegExp"), let R = separator;
1180 // otherwise let R = ToString(separator).
1181 JSValue separatorValue = exec->uncheckedArgument(0);
1182 String separator = separatorValue.toWTFString(exec);
1183 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1185 // 10. If lim == 0, return A.
1187 return JSValue::encode(result);
1189 // 11. If separator is undefined, then
1190 if (separatorValue.isUndefined()) {
1191 // a. Call the [[DefineOwnProperty]] internal method of A with arguments "0",
1193 result->putDirectIndex(exec, 0, jsStringWithReuse(exec, thisValue, input));
1195 return JSValue::encode(result);
1198 // 12. If s == 0, then
1199 if (input.isEmpty()) {
1200 // a. Let z be SplitMatch(S, 0, R) where S is input, R is separator.
1201 // b. If z is not false, return A.
1202 // c. Call CreateDataProperty(A, "0", S).
1204 if (!separator.isEmpty()) {
1206 result->putDirectIndex(exec, 0, jsStringWithReuse(exec, thisValue, input));
1208 return JSValue::encode(result);
1211 // Optimized case for splitting on the empty string.
1212 if (separator.isEmpty()) {
1213 limit = std::min(limit, input.length());
1214 // Zero limt/input length handled in steps 9/11 respectively, above.
1218 result->putDirectIndex(exec, position, jsSingleCharacterString(exec, input[position]));
1219 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1220 } while (++position < limit);
1222 return JSValue::encode(result);
1226 // -separator length == 1, 8 bits
1227 // -separator length == 1, 16 bits
1228 // -separator length > 1
1229 StringImpl* stringImpl = input.impl();
1230 StringImpl* separatorImpl = separator.impl();
1231 size_t separatorLength = separatorImpl->length();
1233 if (separatorLength == 1) {
1234 UChar separatorCharacter;
1235 if (separatorImpl->is8Bit())
1236 separatorCharacter = separatorImpl->characters8()[0];
1238 separatorCharacter = separatorImpl->characters16()[0];
1240 if (stringImpl->is8Bit()) {
1241 if (splitStringByOneCharacterImpl<LChar>(exec, result, thisValue, input, stringImpl, separatorCharacter, position, resultLength, limit)) {
1243 return JSValue::encode(result);
1246 if (splitStringByOneCharacterImpl<UChar>(exec, result, thisValue, input, stringImpl, separatorCharacter, position, resultLength, limit)) {
1248 return JSValue::encode(result);
1251 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1254 size_t matchPosition;
1255 // 14. Repeat, while q != s
1256 // a. let e be SplitMatch(S, q, R).
1257 // b. If e is failure, then let q = q+1.
1258 // c. Else, e is an integer index <= s.
1259 while ((matchPosition = stringImpl->find(separatorImpl, position)) != notFound) {
1260 // 1. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1261 // through q (exclusive).
1262 // 2. Call CreateDataProperty(A, ToString(lengthA), T).
1263 result->putDirectIndex(exec, resultLength, jsSubstring(exec, thisValue, input, position, matchPosition - position));
1264 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1265 // 3. Increment lengthA by 1.
1266 // 4. If lengthA == lim, return A.
1267 if (++resultLength == limit)
1268 return JSValue::encode(result);
1272 position = matchPosition + separator.length();
1276 // 15. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1277 // through s (exclusive).
1278 // 16. Call CreateDataProperty(A, ToString(lengthA), T).
1280 result->putDirectIndex(exec, resultLength++, jsSubstring(exec, thisValue, input, position, input.length() - position));
1283 return JSValue::encode(result);
1286 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState* exec)
1288 VM& vm = exec->vm();
1289 auto scope = DECLARE_THROW_SCOPE(vm);
1291 JSValue thisValue = exec->thisValue();
1292 if (!checkObjectCoercible(thisValue))
1293 return throwVMTypeError(exec, scope);
1295 JSString* jsString = 0;
1297 if (thisValue.isString()) {
1298 jsString = asString(thisValue);
1299 len = jsString->length();
1301 uString = thisValue.toWTFString(exec);
1302 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1303 len = uString.length();
1306 JSValue a0 = exec->argument(0);
1307 JSValue a1 = exec->argument(1);
1309 double start = a0.toInteger(exec);
1310 double length = a1.isUndefined() ? len : a1.toInteger(exec);
1311 if (start >= len || length <= 0)
1312 return JSValue::encode(jsEmptyString(exec));
1318 if (start + length > len)
1319 length = len - start;
1320 unsigned substringStart = static_cast<unsigned>(start);
1321 unsigned substringLength = static_cast<unsigned>(length);
1323 return JSValue::encode(jsSubstring(exec, jsString, substringStart, substringLength));
1324 return JSValue::encode(jsSubstring(exec, uString, substringStart, substringLength));
1327 EncodedJSValue JSC_HOST_CALL builtinStringSubstrInternal(ExecState* exec)
1329 // @substrInternal should not have any observable side effects (e.g. it should not call
1330 // GetMethod(..., @@toPrimitive) on the thisValue).
1332 // It is ok to use the default stringProtoFuncSubstr as the implementation of
1333 // @substrInternal because @substrInternal will only be called by builtins, which will
1334 // guarantee that we only pass it a string thisValue. As a result, stringProtoFuncSubstr
1335 // will not need to call toString() on the thisValue, and there will be no observable
1337 ASSERT(exec->thisValue().isString());
1338 return stringProtoFuncSubstr(exec);
1341 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState* exec)
1343 VM& vm = exec->vm();
1344 auto scope = DECLARE_THROW_SCOPE(vm);
1346 JSValue thisValue = exec->thisValue();
1347 if (!checkObjectCoercible(thisValue))
1348 return throwVMTypeError(exec, scope);
1350 JSString* jsString = thisValue.toString(exec);
1351 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1353 JSValue a0 = exec->argument(0);
1354 JSValue a1 = exec->argument(1);
1355 int len = jsString->length();
1356 RELEASE_ASSERT(len >= 0);
1358 double start = a0.toNumber(exec);
1359 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1361 if (!(start >= 0)) // check for negative values or NaN
1363 else if (start > len)
1365 if (a1.isUndefined())
1368 end = a1.toNumber(exec);
1369 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1370 if (!(end >= 0)) // check for negative values or NaN
1380 unsigned substringStart = static_cast<unsigned>(start);
1381 unsigned substringLength = static_cast<unsigned>(end) - substringStart;
1382 return JSValue::encode(jsSubstring(exec, jsString, substringStart, substringLength));
1385 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState* exec)
1387 VM& vm = exec->vm();
1388 auto scope = DECLARE_THROW_SCOPE(vm);
1390 JSValue thisValue = exec->thisValue();
1391 if (!checkObjectCoercible(thisValue))
1392 return throwVMTypeError(exec, scope);
1393 JSString* sVal = thisValue.toString(exec);
1394 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1395 const String& s = sVal->value(exec);
1396 String lowercasedString = s.convertToLowercaseWithoutLocale();
1397 if (lowercasedString.impl() == s.impl())
1398 return JSValue::encode(sVal);
1400 return JSValue::encode(jsString(exec, lowercasedString));
1403 EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState* exec)
1405 VM& vm = exec->vm();
1406 auto scope = DECLARE_THROW_SCOPE(vm);
1408 JSValue thisValue = exec->thisValue();
1409 if (!checkObjectCoercible(thisValue))
1410 return throwVMTypeError(exec, scope);
1411 JSString* sVal = thisValue.toString(exec);
1412 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1413 const String& s = sVal->value(exec);
1414 String uppercasedString = s.convertToUppercaseWithoutLocale();
1415 if (uppercasedString.impl() == s.impl())
1416 return JSValue::encode(sVal);
1418 return JSValue::encode(jsString(exec, uppercasedString));
1421 EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState* exec)
1423 VM& vm = exec->vm();
1424 auto scope = DECLARE_THROW_SCOPE(vm);
1426 JSValue thisValue = exec->thisValue();
1427 if (!checkObjectCoercible(thisValue))
1428 return throwVMTypeError(exec, scope);
1429 String s = thisValue.toWTFString(exec);
1430 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1432 JSValue a0 = exec->argument(0);
1433 String str = a0.toWTFString(exec);
1434 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1435 return JSValue::encode(jsNumber(Collator().collate(s, str)));
1439 static EncodedJSValue toLocaleCase(ExecState* state, int32_t (*convertCase)(UChar*, int32_t, const UChar*, int32_t, const char*, UErrorCode*))
1441 VM& vm = state->vm();
1442 auto scope = DECLARE_THROW_SCOPE(vm);
1444 // 1. Let O be RequireObjectCoercible(this value).
1445 JSValue thisValue = state->thisValue();
1446 if (!checkObjectCoercible(thisValue))
1447 return throwVMTypeError(state, scope);
1449 // 2. Let S be ToString(O).
1450 JSString* sVal = thisValue.toString(state);
1451 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1452 const String& s = sVal->value(state);
1454 // 3. ReturnIfAbrupt(S).
1455 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1457 // Optimization for empty strings.
1459 return JSValue::encode(sVal);
1461 // 4. Let requestedLocales be CanonicalizeLocaleList(locales).
1462 Vector<String> requestedLocales = canonicalizeLocaleList(*state, state->argument(0));
1464 // 5. ReturnIfAbrupt(requestedLocales).
1465 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1467 // 6. Let len be the number of elements in requestedLocales.
1468 size_t len = requestedLocales.size();
1470 // 7. If len > 0, then
1471 // a. Let requestedLocale be the first element of requestedLocales.
1473 // a. Let requestedLocale be DefaultLocale().
1474 String requestedLocale = len > 0 ? requestedLocales.first() : defaultLocale(*state);
1476 // 9. Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences (6.2.1) removed.
1477 String noExtensionsLocale = removeUnicodeLocaleExtension(requestedLocale);
1479 // 10. Let availableLocales be a List with the language tags of the languages for which the Unicode character database contains language sensitive case mappings.
1480 // Note 1: As of Unicode 5.1, the availableLocales list contains the elements "az", "lt", and "tr".
1481 const HashSet<String> availableLocales({ ASCIILiteral("az"), ASCIILiteral("lt"), ASCIILiteral("tr") });
1483 // 11. Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
1484 String locale = bestAvailableLocale(availableLocales, noExtensionsLocale);
1486 // 12. If locale is undefined, let locale be "und".
1487 if (locale.isNull())
1488 locale = ASCIILiteral("und");
1490 CString utf8LocaleBuffer = locale.utf8();
1491 const StringView view(s);
1492 const int32_t viewLength = view.length();
1494 // Delegate the following steps to icu u_strToLower or u_strToUpper.
1495 // 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.
1496 // 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).
1497 // 15. Let cuList be a new List.
1498 // 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.
1499 // 17. Let L be a String whose elements are, in order, the elements of cuList.
1501 // Most strings lower/upper case will be the same size as original, so try that first.
1502 UErrorCode error(U_ZERO_ERROR);
1503 Vector<UChar> buffer(viewLength);
1505 const int32_t resultLength = convertCase(buffer.data(), viewLength, view.upconvertedCharacters(), viewLength, utf8LocaleBuffer.data(), &error);
1506 if (U_SUCCESS(error))
1507 lower = String(buffer.data(), resultLength);
1508 else if (error == U_BUFFER_OVERFLOW_ERROR) {
1509 // Converted case needs more space than original. Try again.
1510 UErrorCode error(U_ZERO_ERROR);
1511 Vector<UChar> buffer(resultLength);
1512 convertCase(buffer.data(), resultLength, view.upconvertedCharacters(), viewLength, utf8LocaleBuffer.data(), &error);
1513 if (U_FAILURE(error))
1514 return throwVMTypeError(state, scope, u_errorName(error));
1515 lower = String(buffer.data(), resultLength);
1517 return throwVMTypeError(state, scope, u_errorName(error));
1521 return JSValue::encode(jsString(state, lower));
1524 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleLowerCase(ExecState* state)
1526 // 13.1.2 String.prototype.toLocaleLowerCase ([locales])
1527 // http://ecma-international.org/publications/standards/Ecma-402.htm
1528 return toLocaleCase(state, u_strToLower);
1531 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleUpperCase(ExecState* state)
1533 // 13.1.3 String.prototype.toLocaleUpperCase ([locales])
1534 // http://ecma-international.org/publications/standards/Ecma-402.htm
1535 // 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.
1536 return toLocaleCase(state, u_strToUpper);
1538 #endif // ENABLE(INTL)
1545 static inline JSValue trimString(ExecState* exec, JSValue thisValue, int trimKind)
1547 VM& vm = exec->vm();
1548 auto scope = DECLARE_THROW_SCOPE(vm);
1550 if (!checkObjectCoercible(thisValue))
1551 return throwTypeError(exec, scope);
1552 String str = thisValue.toWTFString(exec);
1553 RETURN_IF_EXCEPTION(scope, { });
1556 if (trimKind & TrimLeft) {
1557 while (left < str.length() && isStrWhiteSpace(str[left]))
1560 unsigned right = str.length();
1561 if (trimKind & TrimRight) {
1562 while (right > left && isStrWhiteSpace(str[right - 1]))
1566 // Don't gc allocate a new string if we don't have to.
1567 if (left == 0 && right == str.length() && thisValue.isString())
1571 return jsString(exec, str.substringSharingImpl(left, right - left));
1574 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState* exec)
1576 JSValue thisValue = exec->thisValue();
1577 return JSValue::encode(trimString(exec, thisValue, TrimLeft | TrimRight));
1580 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState* exec)
1582 JSValue thisValue = exec->thisValue();
1583 return JSValue::encode(trimString(exec, thisValue, TrimLeft));
1586 EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState* exec)
1588 JSValue thisValue = exec->thisValue();
1589 return JSValue::encode(trimString(exec, thisValue, TrimRight));
1592 static inline unsigned clampAndTruncateToUnsigned(double value, unsigned min, unsigned max)
1598 return static_cast<unsigned>(value);
1601 EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState* exec)
1603 VM& vm = exec->vm();
1604 auto scope = DECLARE_THROW_SCOPE(vm);
1606 JSValue thisValue = exec->thisValue();
1607 if (!checkObjectCoercible(thisValue))
1608 return throwVMTypeError(exec, scope);
1610 String stringToSearchIn = thisValue.toWTFString(exec);
1611 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1613 JSValue a0 = exec->argument(0);
1614 bool isRegularExpression = isRegExp(vm, exec, a0);
1615 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1616 if (isRegularExpression)
1617 return throwVMTypeError(exec, scope, "Argument to String.prototype.startsWith cannot be a RegExp");
1619 String searchString = a0.toWTFString(exec);
1620 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1622 JSValue positionArg = exec->argument(1);
1624 if (positionArg.isInt32())
1625 start = std::max(0, positionArg.asInt32());
1627 unsigned length = stringToSearchIn.length();
1628 start = clampAndTruncateToUnsigned(positionArg.toInteger(exec), 0, length);
1629 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1632 return JSValue::encode(jsBoolean(stringToSearchIn.hasInfixStartingAt(searchString, start)));
1635 EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState* exec)
1637 VM& vm = exec->vm();
1638 auto scope = DECLARE_THROW_SCOPE(vm);
1640 JSValue thisValue = exec->thisValue();
1641 if (!checkObjectCoercible(thisValue))
1642 return throwVMTypeError(exec, scope);
1644 String stringToSearchIn = thisValue.toWTFString(exec);
1645 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1647 JSValue a0 = exec->argument(0);
1648 bool isRegularExpression = isRegExp(vm, exec, a0);
1649 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1650 if (isRegularExpression)
1651 return throwVMTypeError(exec, scope, "Argument to String.prototype.endsWith cannot be a RegExp");
1653 String searchString = a0.toWTFString(exec);
1654 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1656 unsigned length = stringToSearchIn.length();
1658 JSValue endPositionArg = exec->argument(1);
1659 unsigned end = length;
1660 if (endPositionArg.isInt32())
1661 end = std::max(0, endPositionArg.asInt32());
1662 else if (!endPositionArg.isUndefined()) {
1663 end = clampAndTruncateToUnsigned(endPositionArg.toInteger(exec), 0, length);
1664 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1667 return JSValue::encode(jsBoolean(stringToSearchIn.hasInfixEndingAt(searchString, std::min(end, length))));
1670 static EncodedJSValue JSC_HOST_CALL stringIncludesImpl(VM& vm, ExecState* exec, String stringToSearchIn, String searchString, JSValue positionArg)
1672 auto scope = DECLARE_THROW_SCOPE(vm);
1674 if (positionArg.isInt32())
1675 start = std::max(0, positionArg.asInt32());
1677 unsigned length = stringToSearchIn.length();
1678 start = clampAndTruncateToUnsigned(positionArg.toInteger(exec), 0, length);
1679 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1682 return JSValue::encode(jsBoolean(stringToSearchIn.contains(searchString, true, start)));
1685 EncodedJSValue JSC_HOST_CALL stringProtoFuncIncludes(ExecState* exec)
1687 VM& vm = exec->vm();
1688 auto scope = DECLARE_THROW_SCOPE(vm);
1690 JSValue thisValue = exec->thisValue();
1691 if (!checkObjectCoercible(thisValue))
1692 return throwVMTypeError(exec, scope);
1694 String stringToSearchIn = thisValue.toWTFString(exec);
1695 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1697 JSValue a0 = exec->argument(0);
1698 bool isRegularExpression = isRegExp(vm, exec, a0);
1699 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1700 if (isRegularExpression)
1701 return throwVMTypeError(exec, scope, "Argument to String.prototype.includes cannot be a RegExp");
1703 String searchString = a0.toWTFString(exec);
1704 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1706 JSValue positionArg = exec->argument(1);
1709 return stringIncludesImpl(vm, exec, stringToSearchIn, searchString, positionArg);
1712 EncodedJSValue JSC_HOST_CALL builtinStringIncludesInternal(ExecState* exec)
1714 VM& vm = exec->vm();
1715 auto scope = DECLARE_THROW_SCOPE(vm);
1717 JSValue thisValue = exec->thisValue();
1718 ASSERT(checkObjectCoercible(thisValue));
1720 String stringToSearchIn = thisValue.toWTFString(exec);
1721 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1723 JSValue a0 = exec->uncheckedArgument(0);
1724 String searchString = a0.toWTFString(exec);
1725 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1727 JSValue positionArg = exec->argument(1);
1730 return stringIncludesImpl(vm, exec, stringToSearchIn, searchString, positionArg);
1733 EncodedJSValue JSC_HOST_CALL stringProtoFuncIterator(ExecState* exec)
1735 VM& vm = exec->vm();
1736 auto scope = DECLARE_THROW_SCOPE(vm);
1738 JSValue thisValue = exec->thisValue();
1739 if (!checkObjectCoercible(thisValue))
1740 return throwVMTypeError(exec, scope);
1741 JSString* string = thisValue.toString(exec);
1742 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1743 return JSValue::encode(JSStringIterator::create(exec, exec->jsCallee()->globalObject()->stringIteratorStructure(), string));
1746 static JSValue normalize(ExecState* exec, const UChar* source, size_t sourceLength, UNormalizationMode form)
1748 VM& vm = exec->vm();
1749 auto scope = DECLARE_THROW_SCOPE(vm);
1751 UErrorCode status = U_ZERO_ERROR;
1752 int32_t normalizedStringLength = unorm_normalize(source, sourceLength, form, 0, nullptr, 0, &status);
1754 if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
1755 // The behavior is not specified when normalize fails.
1756 // Now we throw a type erorr since it seems that the contents of the string are invalid.
1757 return throwTypeError(exec, scope);
1760 UChar* buffer = nullptr;
1761 auto impl = StringImpl::tryCreateUninitialized(normalizedStringLength, buffer);
1763 return throwOutOfMemoryError(exec, scope);
1765 status = U_ZERO_ERROR;
1766 unorm_normalize(source, sourceLength, form, 0, buffer, normalizedStringLength, &status);
1767 if (U_FAILURE(status))
1768 return throwTypeError(exec, scope);
1771 return jsString(exec, WTFMove(impl));
1774 EncodedJSValue JSC_HOST_CALL stringProtoFuncNormalize(ExecState* exec)
1776 VM& vm = exec->vm();
1777 auto scope = DECLARE_THROW_SCOPE(vm);
1779 JSValue thisValue = exec->thisValue();
1780 if (!checkObjectCoercible(thisValue))
1781 return throwVMTypeError(exec, scope);
1782 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
1783 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1784 StringView view = viewWithString.view;
1786 UNormalizationMode form = UNORM_NFC;
1787 // Verify that the argument is provided and is not undefined.
1788 if (!exec->argument(0).isUndefined()) {
1789 String formString = exec->uncheckedArgument(0).toWTFString(exec);
1790 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1792 if (formString == "NFC")
1794 else if (formString == "NFD")
1796 else if (formString == "NFKC")
1798 else if (formString == "NFKD")
1801 return throwVMError(exec, scope, createRangeError(exec, ASCIILiteral("argument does not match any normalization form")));
1805 return JSValue::encode(normalize(exec, view.upconvertedCharacters(), view.length(), form));