1/*
2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3 * Copyright (C) 2004-2019 Apple Inc. All rights reserved.
4 * Copyright (C) 2009 Torch Mobile, Inc.
5 * Copyright (C) 2015 Jordan Harband (ljharb@gmail.com)
6 *
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.
11 *
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.
16 *
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
20 *
21 */
22
23#include "config.h"
24#include "StringPrototype.h"
25
26#include "BuiltinNames.h"
27#include "ButterflyInlines.h"
28#include "CachedCall.h"
29#include "Error.h"
30#include "FrameTracers.h"
31#include "InterpreterInlines.h"
32#include "IntlCollator.h"
33#include "IntlObject.h"
34#include "JITCodeInlines.h"
35#include "JSArray.h"
36#include "JSCBuiltins.h"
37#include "JSCInlines.h"
38#include "JSFunction.h"
39#include "JSGlobalObjectFunctions.h"
40#include "JSStringIterator.h"
41#include "Lookup.h"
42#include "ObjectPrototype.h"
43#include "ParseInt.h"
44#include "PropertyNameArray.h"
45#include "RegExpCache.h"
46#include "RegExpConstructor.h"
47#include "RegExpGlobalDataInlines.h"
48#include "StringPrototypeInlines.h"
49#include "SuperSampler.h"
50#include <algorithm>
51#include <unicode/uconfig.h>
52#include <unicode/unorm2.h>
53#include <unicode/ustring.h>
54#include <wtf/ASCIICType.h>
55#include <wtf/MathExtras.h>
56#include <wtf/text/StringBuilder.h>
57#include <wtf/text/StringView.h>
58#include <wtf/unicode/Collator.h>
59
60namespace JSC {
61
62STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(StringPrototype);
63
64EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState*);
65EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState*);
66EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState*);
67EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState*);
68EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState*);
69EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*);
70EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState*);
71EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState*);
72EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState*);
73EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState*);
74EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState*);
75EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState*);
76EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState*);
77EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState*);
78EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleLowerCase(ExecState*);
79EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleUpperCase(ExecState*);
80EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState*);
81EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimStart(ExecState*);
82EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimEnd(ExecState*);
83EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState*);
84EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState*);
85EncodedJSValue JSC_HOST_CALL stringProtoFuncIncludes(ExecState*);
86EncodedJSValue JSC_HOST_CALL stringProtoFuncNormalize(ExecState*);
87EncodedJSValue JSC_HOST_CALL stringProtoFuncIterator(ExecState*);
88
89}
90
91#include "StringPrototype.lut.h"
92
93namespace JSC {
94
95const ClassInfo StringPrototype::s_info = { "String", &StringObject::s_info, &stringPrototypeTable, nullptr, CREATE_METHOD_TABLE(StringPrototype) };
96
97/* Source for StringConstructor.lut.h
98@begin stringPrototypeTable
99 concat JSBuiltin DontEnum|Function 1
100 match JSBuiltin DontEnum|Function 1
101 padStart JSBuiltin DontEnum|Function 1
102 padEnd JSBuiltin DontEnum|Function 1
103 repeat JSBuiltin DontEnum|Function 1
104 replace JSBuiltin DontEnum|Function 2
105 search JSBuiltin DontEnum|Function 1
106 split JSBuiltin DontEnum|Function 1
107 anchor JSBuiltin DontEnum|Function 1
108 big JSBuiltin DontEnum|Function 0
109 bold JSBuiltin DontEnum|Function 0
110 blink JSBuiltin DontEnum|Function 0
111 fixed JSBuiltin DontEnum|Function 0
112 fontcolor JSBuiltin DontEnum|Function 1
113 fontsize JSBuiltin DontEnum|Function 1
114 italics JSBuiltin DontEnum|Function 0
115 link JSBuiltin DontEnum|Function 1
116 small JSBuiltin DontEnum|Function 0
117 strike JSBuiltin DontEnum|Function 0
118 sub JSBuiltin DontEnum|Function 0
119 sup JSBuiltin DontEnum|Function 0
120@end
121*/
122
123// ECMA 15.5.4
124StringPrototype::StringPrototype(VM& vm, Structure* structure)
125 : StringObject(vm, structure)
126{
127}
128
129void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSString* nameAndMessage)
130{
131 Base::finishCreation(vm, nameAndMessage);
132 ASSERT(inherits(vm, info()));
133
134 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, stringProtoFuncToString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, StringPrototypeValueOfIntrinsic);
135 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->valueOf, stringProtoFuncToString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, StringPrototypeValueOfIntrinsic);
136 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("charAt", stringProtoFuncCharAt, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, CharAtIntrinsic);
137 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("charCodeAt", stringProtoFuncCharCodeAt, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, CharCodeAtIntrinsic);
138 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("codePointAt", stringProtoFuncCodePointAt, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
139 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("indexOf", stringProtoFuncIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
140 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", stringProtoFuncLastIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
141 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingRegExpPrivateName(), stringProtoFuncReplaceUsingRegExp, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, StringPrototypeReplaceRegExpIntrinsic);
142 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingStringSearchPrivateName(), stringProtoFuncReplaceUsingStringSearch, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
143 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, StringPrototypeSliceIntrinsic);
144 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substr", stringProtoFuncSubstr, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
145 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substring", stringProtoFuncSubstring, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
146 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, StringPrototypeToLowerCaseIntrinsic);
147 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toUpperCase", stringProtoFuncToUpperCase, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
148 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringProtoFuncLocaleCompare, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
149#if ENABLE(INTL)
150 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleLowerCase", stringProtoFuncToLocaleLowerCase, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
151 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleUpperCase", stringProtoFuncToLocaleUpperCase, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
152#else
153 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleLowerCase", stringProtoFuncToLowerCase, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
154 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toLocaleUpperCase", stringProtoFuncToUpperCase, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
155#endif
156 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("trim", stringProtoFuncTrim, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
157 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("startsWith", stringProtoFuncStartsWith, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
158 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("endsWith", stringProtoFuncEndsWith, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
159 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("includes", stringProtoFuncIncludes, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
160 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("normalize", stringProtoFuncNormalize, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
161 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().charCodeAtPrivateName(), stringProtoFuncCharCodeAt, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, CharCodeAtIntrinsic);
162
163 JSFunction* trimStartFunction = JSFunction::create(vm, globalObject, 0, "trimStart"_s, stringProtoFuncTrimStart, NoIntrinsic);
164 JSFunction* trimEndFunction = JSFunction::create(vm, globalObject, 0, "trimEnd"_s, stringProtoFuncTrimEnd, NoIntrinsic);
165 putDirectWithoutTransition(vm, Identifier::fromString(&vm, "trimStart"), trimStartFunction, static_cast<unsigned>(PropertyAttribute::DontEnum));
166 putDirectWithoutTransition(vm, Identifier::fromString(&vm, "trimLeft"), trimStartFunction, static_cast<unsigned>(PropertyAttribute::DontEnum));
167 putDirectWithoutTransition(vm, Identifier::fromString(&vm, "trimEnd"), trimEndFunction, static_cast<unsigned>(PropertyAttribute::DontEnum));
168 putDirectWithoutTransition(vm, Identifier::fromString(&vm, "trimRight"), trimEndFunction, static_cast<unsigned>(PropertyAttribute::DontEnum));
169
170 JSFunction* iteratorFunction = JSFunction::create(vm, globalObject, 0, "[Symbol.iterator]"_s, stringProtoFuncIterator, NoIntrinsic);
171 putDirectWithoutTransition(vm, vm.propertyNames->iteratorSymbol, iteratorFunction, static_cast<unsigned>(PropertyAttribute::DontEnum));
172
173 // The constructor will be added later, after StringConstructor has been built
174 putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum);
175}
176
177StringPrototype* StringPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
178{
179 JSString* empty = jsEmptyString(&vm);
180 StringPrototype* prototype = new (NotNull, allocateCell<StringPrototype>(vm.heap)) StringPrototype(vm, structure);
181 prototype->finishCreation(vm, globalObject, empty);
182 return prototype;
183}
184
185// ------------------------------ Functions --------------------------
186
187static NEVER_INLINE void substituteBackreferencesSlow(StringBuilder& result, StringView replacement, StringView source, const int* ovector, RegExp* reg, size_t i)
188{
189 bool hasNamedCaptures = reg && reg->hasNamedCaptures();
190 int offset = 0;
191 do {
192 if (i + 1 == replacement.length())
193 break;
194
195 UChar ref = replacement[i + 1];
196 if (ref == '$') {
197 // "$$" -> "$"
198 ++i;
199 result.append(replacement.substring(offset, i - offset));
200 offset = i + 1;
201 continue;
202 }
203
204 int backrefStart;
205 int backrefLength;
206 int advance = 0;
207 if (ref == '&') {
208 backrefStart = ovector[0];
209 backrefLength = ovector[1] - backrefStart;
210 } else if (ref == '`') {
211 backrefStart = 0;
212 backrefLength = ovector[0];
213 } else if (ref == '\'') {
214 backrefStart = ovector[1];
215 backrefLength = source.length() - backrefStart;
216 } else if (reg && ref == '<') {
217 // Named back reference
218 if (!hasNamedCaptures) {
219 result.append(replacement.substring(i, 2));
220 offset = i + 2;
221 advance = 1;
222 continue;
223 }
224
225 size_t closingBracket = replacement.find('>', i + 2);
226 if (closingBracket == WTF::notFound) {
227 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=176434
228 // Current proposed spec change throws a syntax error in this case.
229 // We have made the case that it makes more sense to treat this a literal
230 // If throwSyntaxError(exec, scope, "Missing closing '>' in replacement text");
231 continue;
232 }
233
234 unsigned nameLength = closingBracket - i - 2;
235 unsigned backrefIndex = reg->subpatternForName(replacement.substring(i + 2, nameLength).toString());
236
237 if (!backrefIndex || backrefIndex > reg->numSubpatterns()) {
238 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=176434
239 // Proposed spec change throws a throw syntax error in this case.
240 // We have made the case that a non-existent back reference should be replaced with
241 // and empty string.
242 // throwSyntaxError(exec, scope, makeString("Replacement text references non-existent backreference \"" + replacement.substring(i + 2, nameLength).toString()));
243 backrefStart = 0;
244 backrefLength = 0;
245 } else {
246 backrefStart = ovector[2 * backrefIndex];
247 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
248 }
249 advance = nameLength + 1;
250 } else if (reg && isASCIIDigit(ref)) {
251 // 1- and 2-digit back references are allowed
252 unsigned backrefIndex = ref - '0';
253 if (backrefIndex > reg->numSubpatterns())
254 continue;
255 if (replacement.length() > i + 2) {
256 ref = replacement[i + 2];
257 if (isASCIIDigit(ref)) {
258 backrefIndex = 10 * backrefIndex + ref - '0';
259 if (backrefIndex > reg->numSubpatterns())
260 backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference
261 else
262 advance = 1;
263 }
264 }
265 if (!backrefIndex)
266 continue;
267 backrefStart = ovector[2 * backrefIndex];
268 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
269 } else
270 continue;
271
272 if (i - offset)
273 result.append(replacement.substring(offset, i - offset));
274 i += 1 + advance;
275 offset = i + 1;
276 if (backrefStart >= 0)
277 result.append(source.substring(backrefStart, backrefLength));
278 } while ((i = replacement.find('$', i + 1)) != notFound);
279
280 if (replacement.length() - offset)
281 result.append(replacement.substring(offset));
282}
283
284inline void substituteBackreferencesInline(StringBuilder& result, const String& replacement, StringView source, const int* ovector, RegExp* reg)
285{
286 size_t i = replacement.find('$');
287 if (UNLIKELY(i != notFound))
288 return substituteBackreferencesSlow(result, replacement, source, ovector, reg, i);
289
290 result.append(replacement);
291}
292
293void substituteBackreferences(StringBuilder& result, const String& replacement, StringView source, const int* ovector, RegExp* reg)
294{
295 substituteBackreferencesInline(result, replacement, source, ovector, reg);
296}
297
298struct StringRange {
299 StringRange(int pos, int len)
300 : position(pos)
301 , length(len)
302 {
303 }
304
305 StringRange()
306 {
307 }
308
309 int position;
310 int length;
311};
312
313static ALWAYS_INLINE JSString* jsSpliceSubstrings(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount)
314{
315 VM& vm = exec->vm();
316 auto scope = DECLARE_THROW_SCOPE(vm);
317
318 if (rangeCount == 1) {
319 int sourceSize = source.length();
320 int position = substringRanges[0].position;
321 int length = substringRanges[0].length;
322 if (position <= 0 && length >= sourceSize)
323 return sourceVal;
324 // We could call String::substringSharingImpl(), but this would result in redundant checks.
325 RELEASE_AND_RETURN(scope, jsString(exec, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length))));
326 }
327
328 // We know that the sum of substringRanges lengths cannot exceed length of
329 // source because the substringRanges were computed from the source string
330 // in removeUsingRegExpSearch(). Hence, totalLength cannot exceed
331 // String::MaxLength, and therefore, cannot overflow.
332 Checked<int, AssertNoOverflow> totalLength = 0;
333 for (int i = 0; i < rangeCount; i++)
334 totalLength += substringRanges[i].length;
335 ASSERT(totalLength <= String::MaxLength);
336
337 if (!totalLength)
338 return jsEmptyString(exec);
339
340 if (source.is8Bit()) {
341 LChar* buffer;
342 const LChar* sourceData = source.characters8();
343 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
344 if (!impl) {
345 throwOutOfMemoryError(exec, scope);
346 return nullptr;
347 }
348
349 Checked<int, AssertNoOverflow> bufferPos = 0;
350 for (int i = 0; i < rangeCount; i++) {
351 if (int srcLen = substringRanges[i].length) {
352 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), sourceData + substringRanges[i].position, srcLen);
353 bufferPos += srcLen;
354 }
355 }
356
357 RELEASE_AND_RETURN(scope, jsString(exec, WTFMove(impl)));
358 }
359
360 UChar* buffer;
361 const UChar* sourceData = source.characters16();
362
363 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
364 if (!impl) {
365 throwOutOfMemoryError(exec, scope);
366 return nullptr;
367 }
368
369 Checked<int, AssertNoOverflow> bufferPos = 0;
370 for (int i = 0; i < rangeCount; i++) {
371 if (int srcLen = substringRanges[i].length) {
372 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), sourceData + substringRanges[i].position, srcLen);
373 bufferPos += srcLen;
374 }
375 }
376
377 RELEASE_AND_RETURN(scope, jsString(exec, WTFMove(impl)));
378}
379
380static ALWAYS_INLINE JSString* jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, const String& source, const StringRange* substringRanges, int rangeCount, const String* separators, int separatorCount)
381{
382 VM& vm = exec->vm();
383 auto scope = DECLARE_THROW_SCOPE(vm);
384
385 if (rangeCount == 1 && separatorCount == 0) {
386 int sourceSize = source.length();
387 int position = substringRanges[0].position;
388 int length = substringRanges[0].length;
389 if (position <= 0 && length >= sourceSize)
390 return sourceVal;
391 // We could call String::substringSharingImpl(), but this would result in redundant checks.
392 RELEASE_AND_RETURN(scope, jsString(exec, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length))));
393 }
394
395 Checked<int, RecordOverflow> totalLength = 0;
396 bool allSeparators8Bit = true;
397 for (int i = 0; i < rangeCount; i++)
398 totalLength += substringRanges[i].length;
399 for (int i = 0; i < separatorCount; i++) {
400 totalLength += separators[i].length();
401 if (separators[i].length() && !separators[i].is8Bit())
402 allSeparators8Bit = false;
403 }
404 if (totalLength.hasOverflowed()) {
405 throwOutOfMemoryError(exec, scope);
406 return nullptr;
407 }
408
409 if (!totalLength)
410 return jsEmptyString(exec);
411
412 if (source.is8Bit() && allSeparators8Bit) {
413 LChar* buffer;
414 const LChar* sourceData = source.characters8();
415
416 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
417 if (!impl) {
418 throwOutOfMemoryError(exec, scope);
419 return nullptr;
420 }
421
422 int maxCount = std::max(rangeCount, separatorCount);
423 Checked<int, AssertNoOverflow> bufferPos = 0;
424 for (int i = 0; i < maxCount; i++) {
425 if (i < rangeCount) {
426 if (int srcLen = substringRanges[i].length) {
427 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), sourceData + substringRanges[i].position, srcLen);
428 bufferPos += srcLen;
429 }
430 }
431 if (i < separatorCount) {
432 if (int sepLen = separators[i].length()) {
433 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), separators[i].characters8(), sepLen);
434 bufferPos += sepLen;
435 }
436 }
437 }
438
439 RELEASE_AND_RETURN(scope, jsString(exec, WTFMove(impl)));
440 }
441
442 UChar* buffer;
443 auto impl = StringImpl::tryCreateUninitialized(totalLength.unsafeGet(), buffer);
444 if (!impl) {
445 throwOutOfMemoryError(exec, scope);
446 return nullptr;
447 }
448
449 int maxCount = std::max(rangeCount, separatorCount);
450 Checked<int, AssertNoOverflow> bufferPos = 0;
451 for (int i = 0; i < maxCount; i++) {
452 if (i < rangeCount) {
453 if (int srcLen = substringRanges[i].length) {
454 if (source.is8Bit())
455 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), source.characters8() + substringRanges[i].position, srcLen);
456 else
457 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), source.characters16() + substringRanges[i].position, srcLen);
458 bufferPos += srcLen;
459 }
460 }
461 if (i < separatorCount) {
462 if (int sepLen = separators[i].length()) {
463 if (separators[i].is8Bit())
464 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), separators[i].characters8(), sepLen);
465 else
466 StringImpl::copyCharacters(buffer + bufferPos.unsafeGet(), separators[i].characters16(), sepLen);
467 bufferPos += sepLen;
468 }
469 }
470 }
471
472 RELEASE_AND_RETURN(scope, jsString(exec, WTFMove(impl)));
473}
474
475#define OUT_OF_MEMORY(exec__, scope__) \
476 do { \
477 throwOutOfMemoryError(exec__, scope__); \
478 return nullptr; \
479 } while (false)
480
481static ALWAYS_INLINE JSString* removeUsingRegExpSearch(VM& vm, ExecState* exec, JSString* string, const String& source, RegExp* regExp)
482{
483 auto scope = DECLARE_THROW_SCOPE(vm);
484 SuperSamplerScope superSamplerScope(false);
485
486 size_t lastIndex = 0;
487 unsigned startPosition = 0;
488
489 Vector<StringRange, 16> sourceRanges;
490 JSGlobalObject* globalObject = exec->lexicalGlobalObject();
491 unsigned sourceLen = source.length();
492
493 while (true) {
494 MatchResult result = globalObject->regExpGlobalData().performMatch(vm, globalObject, regExp, string, source, startPosition);
495 RETURN_IF_EXCEPTION(scope, nullptr);
496 if (!result)
497 break;
498
499 if (lastIndex < result.start) {
500 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
501 OUT_OF_MEMORY(exec, scope);
502 }
503 lastIndex = result.end;
504 startPosition = lastIndex;
505
506 // special case of empty match
507 if (result.empty()) {
508 startPosition++;
509 if (startPosition > sourceLen)
510 break;
511 }
512 }
513
514 if (!lastIndex)
515 return string;
516
517 if (static_cast<unsigned>(lastIndex) < sourceLen) {
518 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, sourceLen - lastIndex)))
519 OUT_OF_MEMORY(exec, scope);
520 }
521 RELEASE_AND_RETURN(scope, jsSpliceSubstrings(exec, string, source, sourceRanges.data(), sourceRanges.size()));
522}
523
524static ALWAYS_INLINE JSString* replaceUsingRegExpSearch(
525 VM& vm, ExecState* exec, JSString* string, JSValue searchValue, CallData& callData,
526 CallType callType, String& replacementString, JSValue replaceValue)
527{
528 auto scope = DECLARE_THROW_SCOPE(vm);
529
530 String source = string->value(exec);
531 unsigned sourceLen = source.length();
532 RETURN_IF_EXCEPTION(scope, nullptr);
533 RegExpObject* regExpObject = jsCast<RegExpObject*>(searchValue);
534 RegExp* regExp = regExpObject->regExp();
535 bool global = regExp->global();
536 bool hasNamedCaptures = regExp->hasNamedCaptures();
537
538 if (global) {
539 // ES5.1 15.5.4.10 step 8.a.
540 regExpObject->setLastIndex(exec, 0);
541 RETURN_IF_EXCEPTION(scope, nullptr);
542
543 if (callType == CallType::None && !replacementString.length())
544 RELEASE_AND_RETURN(scope, removeUsingRegExpSearch(vm, exec, string, source, regExp));
545 }
546
547 // FIXME: This is wrong because we may be called directly from the FTL.
548 // https://bugs.webkit.org/show_bug.cgi?id=154874
549 JSGlobalObject* globalObject = exec->lexicalGlobalObject();
550
551 size_t lastIndex = 0;
552 unsigned startPosition = 0;
553
554 Vector<StringRange, 16> sourceRanges;
555 Vector<String, 16> replacements;
556
557 // This is either a loop (if global is set) or a one-way (if not).
558 if (global && callType == CallType::JS) {
559 // regExp->numSubpatterns() + 1 for pattern args, + 2 for match start and string
560 int argCount = regExp->numSubpatterns() + 1 + 2;
561 if (hasNamedCaptures)
562 ++argCount;
563 JSFunction* func = jsCast<JSFunction*>(replaceValue);
564 CachedCall cachedCall(exec, func, argCount);
565 RETURN_IF_EXCEPTION(scope, nullptr);
566 while (true) {
567 int* ovector;
568 MatchResult result = globalObject->regExpGlobalData().performMatch(vm, globalObject, regExp, string, source, startPosition, &ovector);
569 RETURN_IF_EXCEPTION(scope, nullptr);
570 if (!result)
571 break;
572
573 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
574 OUT_OF_MEMORY(exec, scope);
575
576 cachedCall.clearArguments();
577
578 JSObject* groups = nullptr;
579
580 if (hasNamedCaptures) {
581 JSGlobalObject* globalObject = exec->lexicalGlobalObject();
582 groups = JSFinalObject::create(vm, JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 0));
583 }
584
585 for (unsigned i = 0; i < regExp->numSubpatterns() + 1; ++i) {
586 int matchStart = ovector[i * 2];
587 int matchLen = ovector[i * 2 + 1] - matchStart;
588
589 JSValue patternValue;
590
591 if (matchStart < 0)
592 patternValue = jsUndefined();
593 else
594 patternValue = jsSubstring(&vm, source, matchStart, matchLen);
595
596 cachedCall.appendArgument(patternValue);
597
598 if (i && hasNamedCaptures) {
599 String groupName = regExp->getCaptureGroupName(i);
600 if (!groupName.isEmpty())
601 groups->putDirect(vm, Identifier::fromString(&vm, groupName), patternValue);
602 }
603 }
604
605 cachedCall.appendArgument(jsNumber(result.start));
606 cachedCall.appendArgument(string);
607 if (hasNamedCaptures)
608 cachedCall.appendArgument(groups);
609
610 cachedCall.setThis(jsUndefined());
611 if (UNLIKELY(cachedCall.hasOverflowedArguments())) {
612 throwOutOfMemoryError(exec, scope);
613 return nullptr;
614 }
615
616 JSValue jsResult = cachedCall.call();
617 RETURN_IF_EXCEPTION(scope, nullptr);
618 replacements.append(jsResult.toWTFString(exec));
619 RETURN_IF_EXCEPTION(scope, nullptr);
620
621 lastIndex = result.end;
622 startPosition = lastIndex;
623
624 // special case of empty match
625 if (result.empty()) {
626 startPosition++;
627 if (startPosition > sourceLen)
628 break;
629 }
630 }
631 } else {
632 do {
633 int* ovector;
634 MatchResult result = globalObject->regExpGlobalData().performMatch(vm, globalObject, regExp, string, source, startPosition, &ovector);
635 RETURN_IF_EXCEPTION(scope, nullptr);
636 if (!result)
637 break;
638
639 if (callType != CallType::None) {
640 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
641 OUT_OF_MEMORY(exec, scope);
642
643 MarkedArgumentBuffer args;
644 JSObject* groups = nullptr;
645
646 if (hasNamedCaptures) {
647 JSGlobalObject* globalObject = exec->lexicalGlobalObject();
648 groups = JSFinalObject::create(vm, JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 0));
649 }
650
651 for (unsigned i = 0; i < regExp->numSubpatterns() + 1; ++i) {
652 int matchStart = ovector[i * 2];
653 int matchLen = ovector[i * 2 + 1] - matchStart;
654
655 JSValue patternValue;
656
657 if (matchStart < 0)
658 patternValue = jsUndefined();
659 else {
660 patternValue = jsSubstring(&vm, source, matchStart, matchLen);
661 RETURN_IF_EXCEPTION(scope, nullptr);
662 }
663
664 args.append(patternValue);
665
666 if (i && hasNamedCaptures) {
667 String groupName = regExp->getCaptureGroupName(i);
668 if (!groupName.isEmpty())
669 groups->putDirect(vm, Identifier::fromString(&vm, groupName), patternValue);
670 }
671
672 }
673
674 args.append(jsNumber(result.start));
675 args.append(string);
676 if (hasNamedCaptures)
677 args.append(groups);
678 if (UNLIKELY(args.hasOverflowed())) {
679 throwOutOfMemoryError(exec, scope);
680 return nullptr;
681 }
682
683 JSValue replacement = call(exec, replaceValue, callType, callData, jsUndefined(), args);
684 RETURN_IF_EXCEPTION(scope, nullptr);
685 String replacementString = replacement.toWTFString(exec);
686 RETURN_IF_EXCEPTION(scope, nullptr);
687 replacements.append(replacementString);
688 RETURN_IF_EXCEPTION(scope, nullptr);
689 } else {
690 int replLen = replacementString.length();
691 if (lastIndex < result.start || replLen) {
692 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, result.start - lastIndex)))
693 OUT_OF_MEMORY(exec, scope);
694
695 if (replLen) {
696 StringBuilder replacement(StringBuilder::OverflowHandler::RecordOverflow);
697 substituteBackreferences(replacement, replacementString, source, ovector, regExp);
698 if (UNLIKELY(replacement.hasOverflowed()))
699 OUT_OF_MEMORY(exec, scope);
700 replacements.append(replacement.toString());
701 } else
702 replacements.append(String());
703 }
704 }
705
706 lastIndex = result.end;
707 startPosition = lastIndex;
708
709 // special case of empty match
710 if (result.empty()) {
711 startPosition++;
712 if (startPosition > sourceLen)
713 break;
714 }
715 } while (global);
716 }
717
718 if (!lastIndex && replacements.isEmpty())
719 return string;
720
721 if (static_cast<unsigned>(lastIndex) < sourceLen) {
722 if (UNLIKELY(!sourceRanges.tryConstructAndAppend(lastIndex, sourceLen - lastIndex)))
723 OUT_OF_MEMORY(exec, scope);
724 }
725 RELEASE_AND_RETURN(scope, jsSpliceSubstringsWithSeparators(exec, string, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
726}
727
728JSCell* JIT_OPERATION operationStringProtoFuncReplaceRegExpEmptyStr(
729 ExecState* exec, JSString* thisValue, RegExpObject* searchValue)
730{
731 VM& vm = exec->vm();
732 NativeCallFrameTracer tracer(&vm, exec);
733 auto scope = DECLARE_THROW_SCOPE(vm);
734
735 RegExp* regExp = searchValue->regExp();
736 if (regExp->global()) {
737 // ES5.1 15.5.4.10 step 8.a.
738 searchValue->setLastIndex(exec, 0);
739 RETURN_IF_EXCEPTION(scope, nullptr);
740 String source = thisValue->value(exec);
741 RETURN_IF_EXCEPTION(scope, nullptr);
742 RELEASE_AND_RETURN(scope, removeUsingRegExpSearch(vm, exec, thisValue, source, regExp));
743 }
744
745 CallData callData;
746 String replacementString = emptyString();
747 RELEASE_AND_RETURN(scope, replaceUsingRegExpSearch(
748 vm, exec, thisValue, searchValue, callData, CallType::None, replacementString, JSValue()));
749}
750
751JSCell* JIT_OPERATION operationStringProtoFuncReplaceRegExpString(
752 ExecState* exec, JSString* thisValue, RegExpObject* searchValue, JSString* replaceString)
753{
754 VM& vm = exec->vm();
755 NativeCallFrameTracer tracer(&vm, exec);
756
757 CallData callData;
758 String replacementString = replaceString->value(exec);
759 return replaceUsingRegExpSearch(
760 vm, exec, thisValue, searchValue, callData, CallType::None, replacementString, replaceString);
761}
762
763static ALWAYS_INLINE JSString* replaceUsingRegExpSearch(VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
764{
765 auto scope = DECLARE_THROW_SCOPE(vm);
766
767 String replacementString;
768 CallData callData;
769 CallType callType = getCallData(vm, replaceValue, callData);
770 if (callType == CallType::None) {
771 replacementString = replaceValue.toWTFString(exec);
772 RETURN_IF_EXCEPTION(scope, nullptr);
773 }
774
775 RELEASE_AND_RETURN(scope, replaceUsingRegExpSearch(
776 vm, exec, string, searchValue, callData, callType, replacementString, replaceValue));
777}
778
779static ALWAYS_INLINE JSString* replaceUsingStringSearch(VM& vm, ExecState* exec, JSString* jsString, JSValue searchValue, JSValue replaceValue)
780{
781 auto scope = DECLARE_THROW_SCOPE(vm);
782
783 String string = jsString->value(exec);
784 RETURN_IF_EXCEPTION(scope, nullptr);
785 String searchString = searchValue.toWTFString(exec);
786 RETURN_IF_EXCEPTION(scope, nullptr);
787
788 size_t matchStart = string.find(searchString);
789
790 if (matchStart == notFound)
791 return jsString;
792
793 CallData callData;
794 CallType callType = getCallData(vm, replaceValue, callData);
795 if (callType != CallType::None) {
796 MarkedArgumentBuffer args;
797 auto* substring = jsSubstring(&vm, string, matchStart, searchString.impl()->length());
798 RETURN_IF_EXCEPTION(scope, nullptr);
799 args.append(substring);
800 args.append(jsNumber(matchStart));
801 args.append(jsString);
802 ASSERT(!args.hasOverflowed());
803 replaceValue = call(exec, replaceValue, callType, callData, jsUndefined(), args);
804 RETURN_IF_EXCEPTION(scope, nullptr);
805 }
806
807 String replaceString = replaceValue.toWTFString(exec);
808 RETURN_IF_EXCEPTION(scope, nullptr);
809
810 StringImpl* stringImpl = string.impl();
811 String leftPart(StringImpl::createSubstringSharingImpl(*stringImpl, 0, matchStart));
812
813 size_t matchEnd = matchStart + searchString.impl()->length();
814 int ovector[2] = { static_cast<int>(matchStart), static_cast<int>(matchEnd)};
815 String middlePart;
816 if (callType != CallType::None)
817 middlePart = replaceString;
818 else {
819 StringBuilder replacement(StringBuilder::OverflowHandler::RecordOverflow);
820 substituteBackreferences(replacement, replaceString, string, ovector, 0);
821 if (UNLIKELY(replacement.hasOverflowed()))
822 OUT_OF_MEMORY(exec, scope);
823 middlePart = replacement.toString();
824 }
825
826 size_t leftLength = stringImpl->length() - matchEnd;
827 String rightPart(StringImpl::createSubstringSharingImpl(*stringImpl, matchEnd, leftLength));
828 RELEASE_AND_RETURN(scope, JSC::jsString(exec, leftPart, middlePart, rightPart));
829}
830
831static inline bool checkObjectCoercible(JSValue thisValue)
832{
833 if (thisValue.isString())
834 return true;
835
836 if (thisValue.isUndefinedOrNull())
837 return false;
838
839 if (thisValue.isObject() && asObject(thisValue)->isEnvironment())
840 return false;
841
842 return true;
843}
844
845EncodedJSValue JSC_HOST_CALL stringProtoFuncRepeatCharacter(ExecState* exec)
846{
847 VM& vm = exec->vm();
848 auto scope = DECLARE_THROW_SCOPE(vm);
849
850 // For a string which length is single, instead of creating ropes,
851 // allocating a sequential buffer and fill with the repeated string for efficiency.
852 ASSERT(exec->argumentCount() == 2);
853
854 ASSERT(exec->uncheckedArgument(0).isString());
855 JSString* string = asString(exec->uncheckedArgument(0));
856 ASSERT(string->length() == 1);
857
858 JSValue repeatCountValue = exec->uncheckedArgument(1);
859 RELEASE_ASSERT(repeatCountValue.isNumber());
860 int32_t repeatCount;
861 double value = repeatCountValue.asNumber();
862 if (value > JSString::MaxLength)
863 return JSValue::encode(throwOutOfMemoryError(exec, scope));
864 repeatCount = static_cast<int32_t>(value);
865 ASSERT(repeatCount >= 0);
866 ASSERT(!repeatCountValue.isDouble() || repeatCountValue.asDouble() == repeatCount);
867
868 auto viewWithString = string->viewWithUnderlyingString(exec);
869 StringView view = viewWithString.view;
870 ASSERT(view.length() == 1);
871 scope.assertNoException();
872 UChar character = view[0];
873 scope.release();
874 if (!(character & ~0xff))
875 return JSValue::encode(repeatCharacter(*exec, static_cast<LChar>(character), repeatCount));
876 return JSValue::encode(repeatCharacter(*exec, character, repeatCount));
877}
878
879ALWAYS_INLINE JSString* replace(
880 VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
881{
882 if (searchValue.inherits<RegExpObject>(vm))
883 return replaceUsingRegExpSearch(vm, exec, string, searchValue, replaceValue);
884 return replaceUsingStringSearch(vm, exec, string, searchValue, replaceValue);
885}
886
887ALWAYS_INLINE JSString* replace(
888 VM& vm, ExecState* exec, JSValue thisValue, JSValue searchValue, JSValue replaceValue)
889{
890 auto scope = DECLARE_THROW_SCOPE(vm);
891
892 if (!checkObjectCoercible(thisValue)) {
893 throwVMTypeError(exec, scope);
894 return nullptr;
895 }
896 JSString* string = thisValue.toString(exec);
897 RETURN_IF_EXCEPTION(scope, nullptr);
898 RELEASE_AND_RETURN(scope, replace(vm, exec, string, searchValue, replaceValue));
899}
900
901EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState* exec)
902{
903 VM& vm = exec->vm();
904 auto scope = DECLARE_THROW_SCOPE(vm);
905
906 JSString* string = exec->thisValue().toString(exec);
907 RETURN_IF_EXCEPTION(scope, encodedJSValue());
908
909 JSValue searchValue = exec->argument(0);
910 if (!searchValue.inherits<RegExpObject>(vm))
911 return JSValue::encode(jsUndefined());
912
913 RELEASE_AND_RETURN(scope, JSValue::encode(replaceUsingRegExpSearch(vm, exec, string, searchValue, exec->argument(1))));
914}
915
916EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState* exec)
917{
918 VM& vm = exec->vm();
919 auto scope = DECLARE_THROW_SCOPE(vm);
920
921 JSString* string = exec->thisValue().toString(exec);
922 RETURN_IF_EXCEPTION(scope, encodedJSValue());
923
924 RELEASE_AND_RETURN(scope, JSValue::encode(replaceUsingStringSearch(vm, exec, string, exec->argument(0), exec->argument(1))));
925}
926
927JSCell* JIT_OPERATION operationStringProtoFuncReplaceGeneric(
928 ExecState* exec, EncodedJSValue thisValue, EncodedJSValue searchValue,
929 EncodedJSValue replaceValue)
930{
931 VM& vm = exec->vm();
932 NativeCallFrameTracer tracer(&vm, exec);
933
934 return replace(
935 vm, exec, JSValue::decode(thisValue), JSValue::decode(searchValue),
936 JSValue::decode(replaceValue));
937}
938
939EncodedJSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec)
940{
941 VM& vm = exec->vm();
942 auto scope = DECLARE_THROW_SCOPE(vm);
943
944 JSValue thisValue = exec->thisValue();
945 // Also used for valueOf.
946
947 if (thisValue.isString())
948 return JSValue::encode(thisValue);
949
950 auto* stringObject = jsDynamicCast<StringObject*>(vm, thisValue);
951 if (stringObject)
952 return JSValue::encode(stringObject->internalValue());
953
954 return throwVMTypeError(exec, scope);
955}
956
957EncodedJSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState* exec)
958{
959 VM& vm = exec->vm();
960 auto scope = DECLARE_THROW_SCOPE(vm);
961
962 JSValue thisValue = exec->thisValue();
963 if (!checkObjectCoercible(thisValue))
964 return throwVMTypeError(exec, scope);
965 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
966 RETURN_IF_EXCEPTION(scope, encodedJSValue());
967 StringView view = viewWithString.view;
968 RETURN_IF_EXCEPTION(scope, encodedJSValue());
969 JSValue a0 = exec->argument(0);
970 if (a0.isUInt32()) {
971 uint32_t i = a0.asUInt32();
972 if (i < view.length())
973 return JSValue::encode(jsSingleCharacterString(exec, view[i]));
974 return JSValue::encode(jsEmptyString(exec));
975 }
976 double dpos = a0.toInteger(exec);
977 RETURN_IF_EXCEPTION(scope, encodedJSValue());
978 if (dpos >= 0 && dpos < view.length())
979 return JSValue::encode(jsSingleCharacterString(exec, view[static_cast<unsigned>(dpos)]));
980 return JSValue::encode(jsEmptyString(exec));
981}
982
983EncodedJSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState* exec)
984{
985 VM& vm = exec->vm();
986 auto scope = DECLARE_THROW_SCOPE(vm);
987
988 JSValue thisValue = exec->thisValue();
989 if (!checkObjectCoercible(thisValue))
990 return throwVMTypeError(exec, scope);
991 auto viewWithString = thisValue.toString(exec)->viewWithUnderlyingString(exec);
992 RETURN_IF_EXCEPTION(scope, encodedJSValue());
993 StringView view = viewWithString.view;
994 JSValue a0 = exec->argument(0);
995 if (a0.isUInt32()) {
996 uint32_t i = a0.asUInt32();
997 if (i < view.length())
998 return JSValue::encode(jsNumber(view[i]));
999 return JSValue::encode(jsNaN());
1000 }
1001 double dpos = a0.toInteger(exec);
1002 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1003 if (dpos >= 0 && dpos < view.length())
1004 return JSValue::encode(jsNumber(view[static_cast<int>(dpos)]));
1005 return JSValue::encode(jsNaN());
1006}
1007
1008static inline UChar32 codePointAt(const String& string, unsigned position, unsigned length)
1009{
1010 RELEASE_ASSERT(position < length);
1011 if (string.is8Bit())
1012 return string.characters8()[position];
1013 UChar32 character;
1014 U16_NEXT(string.characters16(), position, length, character);
1015 return character;
1016}
1017
1018EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState* exec)
1019{
1020 VM& vm = exec->vm();
1021 auto scope = DECLARE_THROW_SCOPE(vm);
1022
1023 JSValue thisValue = exec->thisValue();
1024 if (!checkObjectCoercible(thisValue))
1025 return throwVMTypeError(exec, scope);
1026
1027 String string = thisValue.toWTFString(exec);
1028 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1029 unsigned length = string.length();
1030
1031 JSValue argument0 = exec->argument(0);
1032 if (argument0.isUInt32()) {
1033 unsigned position = argument0.asUInt32();
1034 if (position < length)
1035 return JSValue::encode(jsNumber(codePointAt(string, position, length)));
1036 return JSValue::encode(jsUndefined());
1037 }
1038
1039 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1040
1041 double doublePosition = argument0.toInteger(exec);
1042 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1043 if (doublePosition >= 0 && doublePosition < length)
1044 return JSValue::encode(jsNumber(codePointAt(string, static_cast<unsigned>(doublePosition), length)));
1045 return JSValue::encode(jsUndefined());
1046}
1047
1048EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState* exec)
1049{
1050 VM& vm = exec->vm();
1051 auto scope = DECLARE_THROW_SCOPE(vm);
1052
1053 JSValue thisValue = exec->thisValue();
1054 if (!checkObjectCoercible(thisValue))
1055 return throwVMTypeError(exec, scope);
1056
1057 JSValue a0 = exec->argument(0);
1058 JSValue a1 = exec->argument(1);
1059
1060 JSString* thisJSString = thisValue.toString(exec);
1061 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1062 JSString* otherJSString = a0.toString(exec);
1063 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1064
1065 unsigned pos = 0;
1066 if (!a1.isUndefined()) {
1067 int len = thisJSString->length();
1068 RELEASE_ASSERT(len >= 0);
1069 if (a1.isUInt32())
1070 pos = std::min<uint32_t>(a1.asUInt32(), len);
1071 else {
1072 double dpos = a1.toInteger(exec);
1073 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1074 if (dpos < 0)
1075 dpos = 0;
1076 else if (dpos > len)
1077 dpos = len;
1078 pos = static_cast<unsigned>(dpos);
1079 }
1080 }
1081
1082 if (thisJSString->length() < otherJSString->length() + pos)
1083 return JSValue::encode(jsNumber(-1));
1084
1085 auto thisViewWithString = thisJSString->viewWithUnderlyingString(exec);
1086 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1087 auto otherViewWithString = otherJSString->viewWithUnderlyingString(exec);
1088 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1089 size_t result = thisViewWithString.view.find(otherViewWithString.view, pos);
1090 if (result == notFound)
1091 return JSValue::encode(jsNumber(-1));
1092 return JSValue::encode(jsNumber(result));
1093}
1094
1095EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState* exec)
1096{
1097 VM& vm = exec->vm();
1098 auto scope = DECLARE_THROW_SCOPE(vm);
1099
1100 JSValue thisValue = exec->thisValue();
1101 if (!checkObjectCoercible(thisValue))
1102 return throwVMTypeError(exec, scope);
1103
1104 JSValue a0 = exec->argument(0);
1105 JSValue a1 = exec->argument(1);
1106
1107 JSString* thisJSString = thisValue.toString(exec);
1108 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1109 unsigned len = thisJSString->length();
1110 JSString* otherJSString = a0.toString(exec);
1111 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1112
1113 double dpos = a1.toIntegerPreserveNaN(exec);
1114 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1115 unsigned startPosition;
1116 if (dpos < 0)
1117 startPosition = 0;
1118 else if (!(dpos <= len)) // true for NaN
1119 startPosition = len;
1120 else
1121 startPosition = static_cast<unsigned>(dpos);
1122
1123 if (len < otherJSString->length())
1124 return JSValue::encode(jsNumber(-1));
1125
1126 String thisString = thisJSString->value(exec);
1127 String otherString = otherJSString->value(exec);
1128 size_t result;
1129 if (!startPosition)
1130 result = thisString.startsWith(otherString) ? 0 : notFound;
1131 else
1132 result = thisString.reverseFind(otherString, startPosition);
1133 if (result == notFound)
1134 return JSValue::encode(jsNumber(-1));
1135 return JSValue::encode(jsNumber(result));
1136}
1137
1138EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState* exec)
1139{
1140 VM& vm = exec->vm();
1141 auto scope = DECLARE_THROW_SCOPE(vm);
1142
1143 JSValue thisValue = exec->thisValue();
1144 if (!checkObjectCoercible(thisValue))
1145 return throwVMTypeError(exec, scope);
1146 JSString* string = thisValue.toString(exec);
1147 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1148
1149 JSValue a0 = exec->argument(0);
1150 JSValue a1 = exec->argument(1);
1151
1152 int length = string->length();
1153 RELEASE_ASSERT(length >= 0);
1154
1155 // The arg processing is very much like ArrayProtoFunc::Slice
1156 double start = a0.toInteger(exec);
1157 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1158 double end = a1.isUndefined() ? length : a1.toInteger(exec);
1159 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1160 RELEASE_AND_RETURN(scope, JSValue::encode(stringSlice(exec, vm, string, length, start, end)));
1161}
1162
1163// Return true in case of early return (resultLength got to limitLength).
1164template<typename CharacterType>
1165static ALWAYS_INLINE bool splitStringByOneCharacterImpl(ExecState* exec, JSArray* result, JSValue originalValue, const String& input, StringImpl* string, UChar separatorCharacter, size_t& position, unsigned& resultLength, unsigned limitLength)
1166{
1167 VM& vm = exec->vm();
1168 auto scope = DECLARE_THROW_SCOPE(vm);
1169
1170 // 12. Let q = p.
1171 size_t matchPosition;
1172 const CharacterType* characters = string->characters<CharacterType>();
1173 // 13. Repeat, while q != s
1174 // a. Call SplitMatch(S, q, R) and let z be its MatchResult result.
1175 // b. If z is failure, then let q = q+1.
1176 // c. Else, z is not failure
1177 while ((matchPosition = WTF::find(characters, string->length(), separatorCharacter, position)) != notFound) {
1178 // 1. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1179 // through q (exclusive).
1180 // 2. Call the [[DefineOwnProperty]] internal method of A with arguments ToString(lengthA),
1181 // Property Descriptor {[[Value]]: T, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
1182 auto* substring = jsSubstring(exec, originalValue, input, position, matchPosition - position);
1183 RETURN_IF_EXCEPTION(scope, false);
1184 result->putDirectIndex(exec, resultLength, substring);
1185 RETURN_IF_EXCEPTION(scope, false);
1186 // 3. Increment lengthA by 1.
1187 // 4. If lengthA == lim, return A.
1188 if (++resultLength == limitLength)
1189 return true;
1190
1191 // 5. Let p = e.
1192 // 8. Let q = p.
1193 position = matchPosition + 1;
1194 }
1195 return false;
1196}
1197
1198// ES 21.1.3.17 String.prototype.split(separator, limit)
1199EncodedJSValue JSC_HOST_CALL stringProtoFuncSplitFast(ExecState* exec)
1200{
1201 VM& vm = exec->vm();
1202 auto scope = DECLARE_THROW_SCOPE(vm);
1203 JSValue thisValue = exec->thisValue();
1204 ASSERT(checkObjectCoercible(thisValue));
1205
1206 // 3. Let S be the result of calling ToString, giving it the this value as its argument.
1207 // 7. Let s be the number of characters in S.
1208 String input = thisValue.toWTFString(exec);
1209 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1210 ASSERT(!input.isNull());
1211
1212 // 4. Let A be a new array created as if by the expression new Array()
1213 // where Array is the standard built-in constructor with that name.
1214 JSArray* result = constructEmptyArray(exec, 0);
1215 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1216
1217 // 5. Let lengthA be 0.
1218 unsigned resultLength = 0;
1219
1220 // 6. If limit is undefined, let lim = 2^32-1; else let lim = ToUint32(limit).
1221 JSValue limitValue = exec->uncheckedArgument(1);
1222 unsigned limit = limitValue.isUndefined() ? 0xFFFFFFFFu : limitValue.toUInt32(exec);
1223 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1224
1225 // 8. Let p = 0.
1226 size_t position = 0;
1227
1228 // 9. If separator is a RegExp object (its [[Class]] is "RegExp"), let R = separator;
1229 // otherwise let R = ToString(separator).
1230 JSValue separatorValue = exec->uncheckedArgument(0);
1231 String separator = separatorValue.toWTFString(exec);
1232 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1233
1234 // 10. If lim == 0, return A.
1235 if (!limit)
1236 return JSValue::encode(result);
1237
1238 // 11. If separator is undefined, then
1239 if (separatorValue.isUndefined()) {
1240 // a. Call the [[DefineOwnProperty]] internal method of A with arguments "0",
1241 scope.release();
1242 result->putDirectIndex(exec, 0, jsStringWithReuse(exec, thisValue, input));
1243 // b. Return A.
1244 return JSValue::encode(result);
1245 }
1246
1247 // 12. If s == 0, then
1248 if (input.isEmpty()) {
1249 // a. Let z be SplitMatch(S, 0, R) where S is input, R is separator.
1250 // b. If z is not false, return A.
1251 // c. Call CreateDataProperty(A, "0", S).
1252 // d. Return A.
1253 if (!separator.isEmpty()) {
1254 scope.release();
1255 result->putDirectIndex(exec, 0, jsStringWithReuse(exec, thisValue, input));
1256 }
1257 return JSValue::encode(result);
1258 }
1259
1260 // Optimized case for splitting on the empty string.
1261 if (separator.isEmpty()) {
1262 limit = std::min(limit, input.length());
1263 // Zero limt/input length handled in steps 9/11 respectively, above.
1264 ASSERT(limit);
1265
1266 do {
1267 result->putDirectIndex(exec, position, jsSingleCharacterString(exec, input[position]));
1268 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1269 } while (++position < limit);
1270
1271 return JSValue::encode(result);
1272 }
1273
1274 // 3 cases:
1275 // -separator length == 1, 8 bits
1276 // -separator length == 1, 16 bits
1277 // -separator length > 1
1278 StringImpl* stringImpl = input.impl();
1279 StringImpl* separatorImpl = separator.impl();
1280 size_t separatorLength = separatorImpl->length();
1281
1282 if (separatorLength == 1) {
1283 UChar separatorCharacter;
1284 if (separatorImpl->is8Bit())
1285 separatorCharacter = separatorImpl->characters8()[0];
1286 else
1287 separatorCharacter = separatorImpl->characters16()[0];
1288
1289 if (stringImpl->is8Bit()) {
1290 if (splitStringByOneCharacterImpl<LChar>(exec, result, thisValue, input, stringImpl, separatorCharacter, position, resultLength, limit))
1291 RELEASE_AND_RETURN(scope, JSValue::encode(result));
1292 } else {
1293 if (splitStringByOneCharacterImpl<UChar>(exec, result, thisValue, input, stringImpl, separatorCharacter, position, resultLength, limit))
1294 RELEASE_AND_RETURN(scope, JSValue::encode(result));
1295 }
1296 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1297 } else {
1298 // 13. Let q = p.
1299 size_t matchPosition;
1300 // 14. Repeat, while q != s
1301 // a. let e be SplitMatch(S, q, R).
1302 // b. If e is failure, then let q = q+1.
1303 // c. Else, e is an integer index <= s.
1304 while ((matchPosition = stringImpl->find(separatorImpl, position)) != notFound) {
1305 // 1. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1306 // through q (exclusive).
1307 // 2. Call CreateDataProperty(A, ToString(lengthA), T).
1308 auto* substring = jsSubstring(exec, thisValue, input, position, matchPosition - position);
1309 RETURN_IF_EXCEPTION(scope, { });
1310 result->putDirectIndex(exec, resultLength, substring);
1311 RETURN_IF_EXCEPTION(scope, { });
1312 // 3. Increment lengthA by 1.
1313 // 4. If lengthA == lim, return A.
1314 if (++resultLength == limit)
1315 return JSValue::encode(result);
1316
1317 // 5. Let p = e.
1318 // 6. Let q = p.
1319 position = matchPosition + separator.length();
1320 }
1321 }
1322
1323 // 15. Let T be a String value equal to the substring of S consisting of the characters at positions p (inclusive)
1324 // through s (exclusive).
1325 // 16. Call CreateDataProperty(A, ToString(lengthA), T).
1326 auto* substring = jsSubstring(exec, thisValue, input, position, input.length() - position);
1327 RETURN_IF_EXCEPTION(scope, { });
1328 scope.release();
1329 result->putDirectIndex(exec, resultLength++, substring);
1330
1331 // 17. Return A.
1332 return JSValue::encode(result);
1333}
1334
1335EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState* exec)
1336{
1337 VM& vm = exec->vm();
1338 auto scope = DECLARE_THROW_SCOPE(vm);
1339
1340 JSValue thisValue = exec->thisValue();
1341 if (!checkObjectCoercible(thisValue))
1342 return throwVMTypeError(exec, scope);
1343 unsigned len;
1344 JSString* jsString = 0;
1345 String uString;
1346 if (thisValue.isString()) {
1347 jsString = asString(thisValue);
1348 len = jsString->length();
1349 } else {
1350 uString = thisValue.toWTFString(exec);
1351 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1352 len = uString.length();
1353 }
1354
1355 JSValue a0 = exec->argument(0);
1356 JSValue a1 = exec->argument(1);
1357
1358 double start = a0.toInteger(exec);
1359 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1360 double length = a1.isUndefined() ? len : a1.toInteger(exec);
1361 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1362 if (start >= len || length <= 0)
1363 return JSValue::encode(jsEmptyString(exec));
1364 if (start < 0) {
1365 start += len;
1366 if (start < 0)
1367 start = 0;
1368 }
1369 if (start + length > len)
1370 length = len - start;
1371 unsigned substringStart = static_cast<unsigned>(start);
1372 unsigned substringLength = static_cast<unsigned>(length);
1373 scope.release();
1374 if (jsString)
1375 return JSValue::encode(jsSubstring(exec, jsString, substringStart, substringLength));
1376 return JSValue::encode(jsSubstring(&vm, uString, substringStart, substringLength));
1377}
1378
1379EncodedJSValue JSC_HOST_CALL builtinStringSubstrInternal(ExecState* exec)
1380{
1381 // @substrInternal should not have any observable side effects (e.g. it should not call
1382 // GetMethod(..., @@toPrimitive) on the thisValue).
1383
1384 // It is ok to use the default stringProtoFuncSubstr as the implementation of
1385 // @substrInternal because @substrInternal will only be called by builtins, which will
1386 // guarantee that we only pass it a string thisValue. As a result, stringProtoFuncSubstr
1387 // will not need to call toString() on the thisValue, and there will be no observable
1388 // side-effects.
1389 ASSERT(exec->thisValue().isString());
1390 return stringProtoFuncSubstr(exec);
1391}
1392
1393EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState* exec)
1394{
1395 VM& vm = exec->vm();
1396 auto scope = DECLARE_THROW_SCOPE(vm);
1397
1398 JSValue thisValue = exec->thisValue();
1399 if (!checkObjectCoercible(thisValue))
1400 return throwVMTypeError(exec, scope);
1401
1402 JSString* jsString = thisValue.toString(exec);
1403 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1404
1405 JSValue a0 = exec->argument(0);
1406 JSValue a1 = exec->argument(1);
1407 int len = jsString->length();
1408 RELEASE_ASSERT(len >= 0);
1409
1410 double start = a0.toNumber(exec);
1411 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1412 double end;
1413 if (!(start >= 0)) // check for negative values or NaN
1414 start = 0;
1415 else if (start > len)
1416 start = len;
1417 if (a1.isUndefined())
1418 end = len;
1419 else {
1420 end = a1.toNumber(exec);
1421 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1422 if (!(end >= 0)) // check for negative values or NaN
1423 end = 0;
1424 else if (end > len)
1425 end = len;
1426 }
1427 if (start > end) {
1428 double temp = end;
1429 end = start;
1430 start = temp;
1431 }
1432 unsigned substringStart = static_cast<unsigned>(start);
1433 unsigned substringLength = static_cast<unsigned>(end) - substringStart;
1434 RELEASE_AND_RETURN(scope, JSValue::encode(jsSubstring(exec, jsString, substringStart, substringLength)));
1435}
1436
1437EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState* exec)
1438{
1439 VM& vm = exec->vm();
1440 auto scope = DECLARE_THROW_SCOPE(vm);
1441
1442 JSValue thisValue = exec->thisValue();
1443 if (!checkObjectCoercible(thisValue))
1444 return throwVMTypeError(exec, scope);
1445 JSString* sVal = thisValue.toString(exec);
1446 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1447 String s = sVal->value(exec);
1448 String lowercasedString = s.convertToLowercaseWithoutLocale();
1449 if (lowercasedString.impl() == s.impl())
1450 return JSValue::encode(sVal);
1451 RELEASE_AND_RETURN(scope, JSValue::encode(jsString(exec, lowercasedString)));
1452}
1453
1454EncodedJSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState* exec)
1455{
1456 VM& vm = exec->vm();
1457 auto scope = DECLARE_THROW_SCOPE(vm);
1458
1459 JSValue thisValue = exec->thisValue();
1460 if (!checkObjectCoercible(thisValue))
1461 return throwVMTypeError(exec, scope);
1462 JSString* sVal = thisValue.toString(exec);
1463 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1464 String s = sVal->value(exec);
1465 String uppercasedString = s.convertToUppercaseWithoutLocale();
1466 if (uppercasedString.impl() == s.impl())
1467 return JSValue::encode(sVal);
1468 RELEASE_AND_RETURN(scope, JSValue::encode(jsString(exec, uppercasedString)));
1469}
1470
1471EncodedJSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState* exec)
1472{
1473 // 13.1.1 String.prototype.localeCompare (that [, locales [, options ]]) (ECMA-402 2.0)
1474 // http://ecma-international.org/publications/standards/Ecma-402.htm
1475
1476 VM& vm = exec->vm();
1477 auto scope = DECLARE_THROW_SCOPE(vm);
1478
1479 // 1. Let O be RequireObjectCoercible(this value).
1480 JSValue thisValue = exec->thisValue();
1481 if (!checkObjectCoercible(thisValue))
1482 return throwVMTypeError(exec, scope, "String.prototype.localeCompare requires that |this| not be null or undefined"_s);
1483
1484 // 2. Let S be ToString(O).
1485 // 3. ReturnIfAbrupt(S).
1486 String string = thisValue.toWTFString(exec);
1487 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1488
1489 // 4. Let That be ToString(that).
1490 // 5. ReturnIfAbrupt(That).
1491 JSValue thatValue = exec->argument(0);
1492 String that = thatValue.toWTFString(exec);
1493 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1494
1495#if ENABLE(INTL)
1496 JSGlobalObject* globalObject = exec->lexicalGlobalObject();
1497 JSValue locales = exec->argument(1);
1498 JSValue options = exec->argument(2);
1499 IntlCollator* collator = nullptr;
1500 if (locales.isUndefined() && options.isUndefined()) {
1501 collator = globalObject->defaultCollator(exec);
1502 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1503 } else {
1504 collator = IntlCollator::create(vm, globalObject->collatorStructure());
1505 collator->initializeCollator(*exec, locales, options);
1506 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1507 }
1508 RELEASE_AND_RETURN(scope, JSValue::encode(collator->compareStrings(*exec, string, that)));
1509#else
1510 return JSValue::encode(jsNumber(Collator().collate(string, that)));
1511#endif
1512}
1513
1514#if ENABLE(INTL)
1515static EncodedJSValue toLocaleCase(ExecState* state, int32_t (*convertCase)(UChar*, int32_t, const UChar*, int32_t, const char*, UErrorCode*))
1516{
1517 VM& vm = state->vm();
1518 auto scope = DECLARE_THROW_SCOPE(vm);
1519
1520 // 1. Let O be RequireObjectCoercible(this value).
1521 JSValue thisValue = state->thisValue();
1522 if (!checkObjectCoercible(thisValue))
1523 return throwVMTypeError(state, scope);
1524
1525 // 2. Let S be ToString(O).
1526 JSString* sVal = thisValue.toString(state);
1527 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1528 const String& s = sVal->value(state);
1529
1530 // 3. ReturnIfAbrupt(S).
1531 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1532
1533 // Optimization for empty strings.
1534 if (s.isEmpty())
1535 return JSValue::encode(sVal);
1536
1537 // 4. Let requestedLocales be CanonicalizeLocaleList(locales).
1538 Vector<String> requestedLocales = canonicalizeLocaleList(*state, state->argument(0));
1539
1540 // 5. ReturnIfAbrupt(requestedLocales).
1541 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1542
1543 // 6. Let len be the number of elements in requestedLocales.
1544 size_t len = requestedLocales.size();
1545
1546 // 7. If len > 0, then
1547 // a. Let requestedLocale be the first element of requestedLocales.
1548 // 8. Else
1549 // a. Let requestedLocale be DefaultLocale().
1550 String requestedLocale = len > 0 ? requestedLocales.first() : defaultLocale(*state);
1551
1552 // 9. Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences (6.2.1) removed.
1553 String noExtensionsLocale = removeUnicodeLocaleExtension(requestedLocale);
1554
1555 // 10. Let availableLocales be a List with the language tags of the languages for which the Unicode character database contains language sensitive case mappings.
1556 // Note 1: As of Unicode 5.1, the availableLocales list contains the elements "az", "lt", and "tr".
1557 const HashSet<String> availableLocales({ "az"_s, "lt"_s, "tr"_s });
1558
1559 // 11. Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
1560 String locale = bestAvailableLocale(availableLocales, noExtensionsLocale);
1561
1562 // 12. If locale is undefined, let locale be "und".
1563 if (locale.isNull())
1564 locale = "und"_s;
1565
1566 CString utf8LocaleBuffer = locale.utf8();
1567 const StringView view(s);
1568 const int32_t viewLength = view.length();
1569
1570 // Delegate the following steps to icu u_strToLower or u_strToUpper.
1571 // 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.
1572 // 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).
1573 // 15. Let cuList be a new List.
1574 // 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.
1575 // 17. Let L be a String whose elements are, in order, the elements of cuList.
1576
1577 // Most strings lower/upper case will be the same size as original, so try that first.
1578 UErrorCode error(U_ZERO_ERROR);
1579 Vector<UChar> buffer(viewLength);
1580 String lower;
1581 const int32_t resultLength = convertCase(buffer.data(), viewLength, view.upconvertedCharacters(), viewLength, utf8LocaleBuffer.data(), &error);
1582 if (U_SUCCESS(error))
1583 lower = String(buffer.data(), resultLength);
1584 else if (error == U_BUFFER_OVERFLOW_ERROR) {
1585 // Converted case needs more space than original. Try again.
1586 UErrorCode error(U_ZERO_ERROR);
1587 Vector<UChar> buffer(resultLength);
1588 convertCase(buffer.data(), resultLength, view.upconvertedCharacters(), viewLength, utf8LocaleBuffer.data(), &error);
1589 if (U_FAILURE(error))
1590 return throwVMTypeError(state, scope, u_errorName(error));
1591 lower = String(buffer.data(), resultLength);
1592 } else
1593 return throwVMTypeError(state, scope, u_errorName(error));
1594
1595 // 18. Return L.
1596 RELEASE_AND_RETURN(scope, JSValue::encode(jsString(state, lower)));
1597}
1598
1599EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleLowerCase(ExecState* state)
1600{
1601 // 13.1.2 String.prototype.toLocaleLowerCase ([locales])
1602 // http://ecma-international.org/publications/standards/Ecma-402.htm
1603 return toLocaleCase(state, u_strToLower);
1604}
1605
1606EncodedJSValue JSC_HOST_CALL stringProtoFuncToLocaleUpperCase(ExecState* state)
1607{
1608 // 13.1.3 String.prototype.toLocaleUpperCase ([locales])
1609 // http://ecma-international.org/publications/standards/Ecma-402.htm
1610 // 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.
1611 return toLocaleCase(state, u_strToUpper);
1612}
1613#endif // ENABLE(INTL)
1614
1615enum {
1616 TrimStart = 1,
1617 TrimEnd = 2
1618};
1619
1620static inline JSValue trimString(ExecState* exec, JSValue thisValue, int trimKind)
1621{
1622 VM& vm = exec->vm();
1623 auto scope = DECLARE_THROW_SCOPE(vm);
1624
1625 if (!checkObjectCoercible(thisValue))
1626 return throwTypeError(exec, scope);
1627 String str = thisValue.toWTFString(exec);
1628 RETURN_IF_EXCEPTION(scope, { });
1629
1630 unsigned left = 0;
1631 if (trimKind & TrimStart) {
1632 while (left < str.length() && isStrWhiteSpace(str[left]))
1633 left++;
1634 }
1635 unsigned right = str.length();
1636 if (trimKind & TrimEnd) {
1637 while (right > left && isStrWhiteSpace(str[right - 1]))
1638 right--;
1639 }
1640
1641 // Don't gc allocate a new string if we don't have to.
1642 if (left == 0 && right == str.length() && thisValue.isString())
1643 return thisValue;
1644
1645 RELEASE_AND_RETURN(scope, jsString(exec, str.substringSharingImpl(left, right - left)));
1646}
1647
1648EncodedJSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState* exec)
1649{
1650 JSValue thisValue = exec->thisValue();
1651 return JSValue::encode(trimString(exec, thisValue, TrimStart | TrimEnd));
1652}
1653
1654EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimStart(ExecState* exec)
1655{
1656 JSValue thisValue = exec->thisValue();
1657 return JSValue::encode(trimString(exec, thisValue, TrimStart));
1658}
1659
1660EncodedJSValue JSC_HOST_CALL stringProtoFuncTrimEnd(ExecState* exec)
1661{
1662 JSValue thisValue = exec->thisValue();
1663 return JSValue::encode(trimString(exec, thisValue, TrimEnd));
1664}
1665
1666static inline unsigned clampAndTruncateToUnsigned(double value, unsigned min, unsigned max)
1667{
1668 if (value < min)
1669 return min;
1670 if (value > max)
1671 return max;
1672 return static_cast<unsigned>(value);
1673}
1674
1675EncodedJSValue JSC_HOST_CALL stringProtoFuncStartsWith(ExecState* exec)
1676{
1677 VM& vm = exec->vm();
1678 auto scope = DECLARE_THROW_SCOPE(vm);
1679
1680 JSValue thisValue = exec->thisValue();
1681 if (!checkObjectCoercible(thisValue))
1682 return throwVMTypeError(exec, scope);
1683
1684 String stringToSearchIn = thisValue.toWTFString(exec);
1685 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1686
1687 JSValue a0 = exec->argument(0);
1688 bool isRegularExpression = isRegExp(vm, exec, a0);
1689 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1690 if (isRegularExpression)
1691 return throwVMTypeError(exec, scope, "Argument to String.prototype.startsWith cannot be a RegExp");
1692
1693 String searchString = a0.toWTFString(exec);
1694 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1695
1696 JSValue positionArg = exec->argument(1);
1697 unsigned start = 0;
1698 if (positionArg.isInt32())
1699 start = std::max(0, positionArg.asInt32());
1700 else {
1701 unsigned length = stringToSearchIn.length();
1702 start = clampAndTruncateToUnsigned(positionArg.toInteger(exec), 0, length);
1703 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1704 }
1705
1706 return JSValue::encode(jsBoolean(stringToSearchIn.hasInfixStartingAt(searchString, start)));
1707}
1708
1709EncodedJSValue JSC_HOST_CALL stringProtoFuncEndsWith(ExecState* exec)
1710{
1711 VM& vm = exec->vm();
1712 auto scope = DECLARE_THROW_SCOPE(vm);
1713
1714 JSValue thisValue = exec->thisValue();
1715 if (!checkObjectCoercible(thisValue))
1716 return throwVMTypeError(exec, scope);
1717
1718 String stringToSearchIn = thisValue.toWTFString(exec);
1719 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1720
1721 JSValue a0 = exec->argument(0);
1722 bool isRegularExpression = isRegExp(vm, exec, a0);
1723 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1724 if (isRegularExpression)
1725 return throwVMTypeError(exec, scope, "Argument to String.prototype.endsWith cannot be a RegExp");
1726
1727 String searchString = a0.toWTFString(exec);
1728 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1729
1730 unsigned length = stringToSearchIn.length();
1731
1732 JSValue endPositionArg = exec->argument(1);
1733 unsigned end = length;
1734 if (endPositionArg.isInt32())
1735 end = std::max(0, endPositionArg.asInt32());
1736 else if (!endPositionArg.isUndefined()) {
1737 end = clampAndTruncateToUnsigned(endPositionArg.toInteger(exec), 0, length);
1738 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1739 }
1740
1741 return JSValue::encode(jsBoolean(stringToSearchIn.hasInfixEndingAt(searchString, std::min(end, length))));
1742}
1743
1744static EncodedJSValue JSC_HOST_CALL stringIncludesImpl(VM& vm, ExecState* exec, String stringToSearchIn, String searchString, JSValue positionArg)
1745{
1746 auto scope = DECLARE_THROW_SCOPE(vm);
1747 unsigned start = 0;
1748 if (positionArg.isInt32())
1749 start = std::max(0, positionArg.asInt32());
1750 else {
1751 unsigned length = stringToSearchIn.length();
1752 start = clampAndTruncateToUnsigned(positionArg.toInteger(exec), 0, length);
1753 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1754 }
1755
1756 return JSValue::encode(jsBoolean(stringToSearchIn.find(searchString, start) != notFound));
1757}
1758
1759EncodedJSValue JSC_HOST_CALL stringProtoFuncIncludes(ExecState* exec)
1760{
1761 VM& vm = exec->vm();
1762 auto scope = DECLARE_THROW_SCOPE(vm);
1763
1764 JSValue thisValue = exec->thisValue();
1765 if (!checkObjectCoercible(thisValue))
1766 return throwVMTypeError(exec, scope);
1767
1768 String stringToSearchIn = thisValue.toWTFString(exec);
1769 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1770
1771 JSValue a0 = exec->argument(0);
1772 bool isRegularExpression = isRegExp(vm, exec, a0);
1773 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1774 if (isRegularExpression)
1775 return throwVMTypeError(exec, scope, "Argument to String.prototype.includes cannot be a RegExp");
1776
1777 String searchString = a0.toWTFString(exec);
1778 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1779
1780 JSValue positionArg = exec->argument(1);
1781
1782 RELEASE_AND_RETURN(scope, stringIncludesImpl(vm, exec, stringToSearchIn, searchString, positionArg));
1783}
1784
1785EncodedJSValue JSC_HOST_CALL builtinStringIncludesInternal(ExecState* exec)
1786{
1787 VM& vm = exec->vm();
1788 auto scope = DECLARE_THROW_SCOPE(vm);
1789
1790 JSValue thisValue = exec->thisValue();
1791 ASSERT(checkObjectCoercible(thisValue));
1792
1793 String stringToSearchIn = thisValue.toWTFString(exec);
1794 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1795
1796 JSValue a0 = exec->uncheckedArgument(0);
1797 String searchString = a0.toWTFString(exec);
1798 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1799
1800 JSValue positionArg = exec->argument(1);
1801
1802 RELEASE_AND_RETURN(scope, stringIncludesImpl(vm, exec, stringToSearchIn, searchString, positionArg));
1803}
1804
1805EncodedJSValue JSC_HOST_CALL stringProtoFuncIterator(ExecState* exec)
1806{
1807 VM& vm = exec->vm();
1808 auto scope = DECLARE_THROW_SCOPE(vm);
1809
1810 JSValue thisValue = exec->thisValue();
1811 if (!checkObjectCoercible(thisValue))
1812 return throwVMTypeError(exec, scope);
1813 JSString* string = thisValue.toString(exec);
1814 RETURN_IF_EXCEPTION(scope, encodedJSValue());
1815 return JSValue::encode(JSStringIterator::create(exec, exec->jsCallee()->globalObject(vm)->stringIteratorStructure(), string));
1816}
1817
1818enum class NormalizationForm { NFC, NFD, NFKC, NFKD };
1819
1820static constexpr bool normalizationAffects8Bit(NormalizationForm form)
1821{
1822 switch (form) {
1823 case NormalizationForm::NFC:
1824 return false;
1825 case NormalizationForm::NFD:
1826 return true;
1827 case NormalizationForm::NFKC:
1828 return false;
1829 case NormalizationForm::NFKD:
1830 return true;
1831 default:
1832 ASSERT_NOT_REACHED();
1833 }
1834 return true;
1835}
1836
1837static const UNormalizer2* normalizer(NormalizationForm form)
1838{
1839 UErrorCode status = U_ZERO_ERROR;
1840 const UNormalizer2* normalizer = nullptr;
1841 switch (form) {
1842 case NormalizationForm::NFC:
1843 normalizer = unorm2_getNFCInstance(&status);
1844 break;
1845 case NormalizationForm::NFD:
1846 normalizer = unorm2_getNFDInstance(&status);
1847 break;
1848 case NormalizationForm::NFKC:
1849 normalizer = unorm2_getNFKCInstance(&status);
1850 break;
1851 case NormalizationForm::NFKD:
1852 normalizer = unorm2_getNFKDInstance(&status);
1853 break;
1854 }
1855 ASSERT(normalizer);
1856 ASSERT(U_SUCCESS(status));
1857 return normalizer;
1858}
1859
1860static JSValue normalize(ExecState* exec, JSString* string, NormalizationForm form)
1861{
1862 VM& vm = exec->vm();
1863 auto scope = DECLARE_THROW_SCOPE(vm);
1864
1865 auto viewWithString = string->viewWithUnderlyingString(exec);
1866 RETURN_IF_EXCEPTION(scope, { });
1867
1868 StringView view = viewWithString.view;
1869 if (view.is8Bit() && (!normalizationAffects8Bit(form) || charactersAreAllASCII(view.characters8(), view.length())))
1870 RELEASE_AND_RETURN(scope, string);
1871
1872 const UNormalizer2* normalizer = JSC::normalizer(form);
1873
1874 // Since ICU does not offer functions that can perform normalization or check for
1875 // normalization with input that is Latin-1, we need to upconvert to UTF-16 at this point.
1876 auto characters = view.upconvertedCharacters();
1877
1878 UErrorCode status = U_ZERO_ERROR;
1879 UBool isNormalized = unorm2_isNormalized(normalizer, characters, view.length(), &status);
1880 ASSERT(U_SUCCESS(status));
1881 if (isNormalized)
1882 RELEASE_AND_RETURN(scope, string);
1883
1884 int32_t normalizedStringLength = unorm2_normalize(normalizer, characters, view.length(), nullptr, 0, &status);
1885 ASSERT(status == U_BUFFER_OVERFLOW_ERROR);
1886
1887 UChar* buffer;
1888 auto result = StringImpl::tryCreateUninitialized(normalizedStringLength, buffer);
1889 if (!result)
1890 return throwOutOfMemoryError(exec, scope);
1891
1892 status = U_ZERO_ERROR;
1893 unorm2_normalize(normalizer, characters, view.length(), buffer, normalizedStringLength, &status);
1894 ASSERT(U_SUCCESS(status));
1895
1896 RELEASE_AND_RETURN(scope, jsString(&vm, WTFMove(result)));
1897}
1898
1899EncodedJSValue JSC_HOST_CALL stringProtoFuncNormalize(ExecState* exec)
1900{
1901 VM& vm = exec->vm();
1902 auto scope = DECLARE_THROW_SCOPE(vm);
1903
1904 JSValue thisValue = exec->thisValue();
1905 if (!checkObjectCoercible(thisValue))
1906 return throwVMTypeError(exec, scope);
1907 JSString* string = thisValue.toString(exec);
1908 RETURN_IF_EXCEPTION(scope, { });
1909
1910 auto form = NormalizationForm::NFC;
1911 JSValue formValue = exec->argument(0);
1912 if (!formValue.isUndefined()) {
1913 String formString = formValue.toWTFString(exec);
1914 RETURN_IF_EXCEPTION(scope, { });
1915
1916 if (formString == "NFC")
1917 form = NormalizationForm::NFC;
1918 else if (formString == "NFD")
1919 form = NormalizationForm::NFD;
1920 else if (formString == "NFKC")
1921 form = NormalizationForm::NFKC;
1922 else if (formString == "NFKD")
1923 form = NormalizationForm::NFKD;
1924 else
1925 return throwVMRangeError(exec, scope, "argument does not match any normalization form"_s);
1926 }
1927
1928 RELEASE_AND_RETURN(scope, JSValue::encode(normalize(exec, string, form)));
1929}
1930
1931} // namespace JSC
1932