Title: [252683] trunk
Revision
252683
Author
ross.kirsl...@sony.com
Date
2019-11-19 21:12:14 -0800 (Tue, 19 Nov 2019)

Log Message

Implement String.prototype.replaceAll
https://bugs.webkit.org/show_bug.cgi?id=202471

Reviewed by Yusuke Suzuki.

JSTests:

* stress/string-replaceall.js: Added.

Source/_javascript_Core:

Implement the stage 3 proposal here:
https://github.com/tc39/proposal-string-replaceall

String.prototype.replaceAll is the same as String.prototype.replace, except:
1. When the first argument is a string, all instances of the search string are replaced.
2. When the first argument is a non-global regular _expression_, a TypeError is thrown.

* builtins/BuiltinNames.h:
* builtins/StringPrototype.js:
(replaceAll): Added.
* runtime/StringPrototype.cpp:
(JSC::StringPrototype::finishCreation):
(JSC::jsSpliceSubstringsWithSeparators): Add early out for single-replacement case.
(JSC::replaceUsingStringSearch): Add global replacement logic, following replaceUsingRegExpSearch.
(JSC::replace):
(JSC::stringProtoFuncReplaceUsingStringSearch):
(JSC::stringProtoFuncReplaceAllUsingStringSearch): Added.

Source/WTF:

* wtf/text/StringCommon.h:
(WTF::findCommon):
Fix logic: "start > length" early out should come before "empty search string" early out.

LayoutTests:

* js/Object-getOwnPropertyNames-expected.txt:
* js/script-tests/Object-getOwnPropertyNames.js:
Grrr, why is this a layout test...

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (252682 => 252683)


--- trunk/JSTests/ChangeLog	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/JSTests/ChangeLog	2019-11-20 05:12:14 UTC (rev 252683)
@@ -1,3 +1,12 @@
+2019-11-19  Ross Kirsling  <ross.kirsl...@sony.com>
+
+        Implement String.prototype.replaceAll
+        https://bugs.webkit.org/show_bug.cgi?id=202471
+
+        Reviewed by Yusuke Suzuki.
+
+        * stress/string-replaceall.js: Added.
+
 2019-11-19  Robin Morisset  <rmoris...@apple.com>
 
         [ESNext][BigInt] Add support for op_inc

Added: trunk/JSTests/stress/string-replaceall.js (0 => 252683)


--- trunk/JSTests/stress/string-replaceall.js	                        (rev 0)
+++ trunk/JSTests/stress/string-replaceall.js	2019-11-20 05:12:14 UTC (rev 252683)
@@ -0,0 +1,36 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+function shouldThrowTypeError(func) {
+    let error;
+    try {
+        func();
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof TypeError))
+        throw new Error('Expected TypeError!');
+}
+
+shouldThrowTypeError(() => { String.prototype.replaceAll.call(undefined, 'def', 'xyz'); });
+shouldThrowTypeError(() => { String.prototype.replaceAll.call(null, 'def', 'xyz'); });
+
+shouldThrowTypeError(() => { 'abcdefabcdefabc'.replaceAll(/def/, 'xyz'); });
+shouldThrowTypeError(() => { 'abcdefabcdefabc'.replaceAll(new RegExp('def'), 'xyz'); });
+shouldThrowTypeError(() => { 'abcdefabcdefabc'.replaceAll({ [Symbol.match]() {}, toString: () => 'def' }, 'xyz'); });
+
+shouldBe('abcdefabcdefabc'.replaceAll('def', 'xyz'), 'abcxyzabcxyzabc');
+shouldBe('abcdefabcdefabc'.replaceAll(/def/g, 'xyz'), 'abcxyzabcxyzabc');
+shouldBe('abcdefabcdefabc'.replaceAll(new RegExp('def', 'g'), 'xyz'), 'abcxyzabcxyzabc');
+shouldBe('abcdefabcdefabc'.replaceAll({ [Symbol.match]() {}, toString: () => 'def', flags: 'g' }, 'xyz'), 'abcxyzabcxyzabc');
+
+const search = /def/g;
+search[Symbol.replace] = undefined;
+shouldBe('abcdefabcdefabc'.replaceAll(search, 'xyz'), 'abcdefabcdefabc');
+search[Symbol.replace] = () => 'q';
+shouldBe('abcdefabcdefabc'.replaceAll(search, 'xyz'), 'q');
+search[Symbol.replace] = RegExp.prototype[Symbol.replace].bind(search);
+shouldBe('abcdefabcdefabc'.replaceAll(search, 'xyz'), 'abcxyzabcxyzabc');

Modified: trunk/LayoutTests/ChangeLog (252682 => 252683)


--- trunk/LayoutTests/ChangeLog	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/LayoutTests/ChangeLog	2019-11-20 05:12:14 UTC (rev 252683)
@@ -1,3 +1,14 @@
+2019-11-19  Ross Kirsling  <ross.kirsl...@sony.com>
+
+        Implement String.prototype.replaceAll
+        https://bugs.webkit.org/show_bug.cgi?id=202471
+
+        Reviewed by Yusuke Suzuki.
+
+        * js/Object-getOwnPropertyNames-expected.txt:
+        * js/script-tests/Object-getOwnPropertyNames.js:
+        Grrr, why is this a layout test...
+
 2019-11-19  Youenn Fablet  <you...@apple.com>
 
         getUserMedia echoCancellation constraint has no affect

Modified: trunk/LayoutTests/js/Object-getOwnPropertyNames-expected.txt (252682 => 252683)


--- trunk/LayoutTests/js/Object-getOwnPropertyNames-expected.txt	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/LayoutTests/js/Object-getOwnPropertyNames-expected.txt	2019-11-20 05:12:14 UTC (rev 252683)
@@ -49,7 +49,7 @@
 PASS getSortedOwnPropertyNames(Array) is ['from', 'isArray', 'length', 'name', 'of', 'prototype']
 PASS getSortedOwnPropertyNames(Array.prototype) is ['concat', 'constructor', 'copyWithin', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toString', 'unshift', 'values']
 PASS getSortedOwnPropertyNames(String) is ['fromCharCode', 'fromCodePoint', 'length', 'name', 'prototype', 'raw']
-PASS getSortedOwnPropertyNames(String.prototype) is ['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'matchAll', 'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimEnd', 'trimLeft', 'trimRight', 'trimStart', 'valueOf']
+PASS getSortedOwnPropertyNames(String.prototype) is ['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'matchAll', 'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'replaceAll', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimEnd', 'trimLeft', 'trimRight', 'trimStart', 'valueOf']
 PASS getSortedOwnPropertyNames(Boolean) is ['length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Boolean.prototype) is ['constructor', 'toString', 'valueOf']
 PASS getSortedOwnPropertyNames(Number) is ['EPSILON', 'MAX_SAFE_INTEGER', 'MAX_VALUE', 'MIN_SAFE_INTEGER', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'NaN', 'POSITIVE_INFINITY', 'isFinite', 'isInteger', 'isNaN', 'isSafeInteger', 'length', 'name', 'parseFloat', 'parseInt', 'prototype']

Modified: trunk/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js (252682 => 252683)


--- trunk/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js	2019-11-20 05:12:14 UTC (rev 252683)
@@ -58,7 +58,7 @@
     "Array": "['from', 'isArray', 'length', 'name', 'of', 'prototype']",
     "Array.prototype": "['concat', 'constructor', 'copyWithin', 'entries', 'every', 'fill', 'filter', 'find', 'findIndex', 'flat', 'flatMap', 'forEach', 'includes', 'indexOf', 'join', 'keys', 'lastIndexOf', 'length', 'map', 'pop', 'push', 'reduce', 'reduceRight', 'reverse', 'shift', 'slice', 'some', 'sort', 'splice', 'toLocaleString', 'toString', 'unshift', 'values']",
     "String": "['fromCharCode', 'fromCodePoint', 'length', 'name', 'prototype', 'raw']",
-    "String.prototype": "['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'matchAll', 'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimEnd', 'trimLeft', 'trimRight', 'trimStart', 'valueOf']",
+    "String.prototype": "['anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'concat', 'constructor', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'length', 'link', 'localeCompare', 'match', 'matchAll', 'normalize', 'padEnd', 'padStart', 'repeat', 'replace', 'replaceAll', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toString', 'toUpperCase', 'trim', 'trimEnd', 'trimLeft', 'trimRight', 'trimStart', 'valueOf']",
     "Boolean": "['length', 'name', 'prototype']",
     "Boolean.prototype": "['constructor', 'toString', 'valueOf']",
     "Number": "['EPSILON', 'MAX_SAFE_INTEGER', 'MAX_VALUE', 'MIN_SAFE_INTEGER', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'NaN', 'POSITIVE_INFINITY', 'isFinite', 'isInteger', 'isNaN', 'isSafeInteger', 'length', 'name', 'parseFloat', 'parseInt', 'prototype']",

Modified: trunk/Source/_javascript_Core/ChangeLog (252682 => 252683)


--- trunk/Source/_javascript_Core/ChangeLog	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-11-20 05:12:14 UTC (rev 252683)
@@ -1,3 +1,28 @@
+2019-11-19  Ross Kirsling  <ross.kirsl...@sony.com>
+
+        Implement String.prototype.replaceAll
+        https://bugs.webkit.org/show_bug.cgi?id=202471
+
+        Reviewed by Yusuke Suzuki.
+
+        Implement the stage 3 proposal here:
+        https://github.com/tc39/proposal-string-replaceall
+
+        String.prototype.replaceAll is the same as String.prototype.replace, except:
+        1. When the first argument is a string, all instances of the search string are replaced.
+        2. When the first argument is a non-global regular _expression_, a TypeError is thrown.
+
+        * builtins/BuiltinNames.h:
+        * builtins/StringPrototype.js:
+        (replaceAll): Added.
+        * runtime/StringPrototype.cpp:
+        (JSC::StringPrototype::finishCreation):
+        (JSC::jsSpliceSubstringsWithSeparators): Add early out for single-replacement case.
+        (JSC::replaceUsingStringSearch): Add global replacement logic, following replaceUsingRegExpSearch.
+        (JSC::replace):
+        (JSC::stringProtoFuncReplaceUsingStringSearch):
+        (JSC::stringProtoFuncReplaceAllUsingStringSearch): Added.
+
 2019-11-19  Robin Morisset  <rmoris...@apple.com>
 
         [ESNext][BigInt] Add support for op_inc

Modified: trunk/Source/_javascript_Core/builtins/BuiltinNames.h (252682 => 252683)


--- trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2019-11-20 05:12:14 UTC (rev 252683)
@@ -132,6 +132,7 @@
     macro(isRegExp) \
     macro(replaceUsingRegExp) \
     macro(replaceUsingStringSearch) \
+    macro(replaceAllUsingStringSearch) \
     macro(makeTypeError) \
     macro(mapBucket) \
     macro(mapBucketHead) \

Modified: trunk/Source/_javascript_Core/builtins/StringPrototype.js (252682 => 252683)


--- trunk/Source/_javascript_Core/builtins/StringPrototype.js	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/Source/_javascript_Core/builtins/StringPrototype.js	2019-11-20 05:12:14 UTC (rev 252683)
@@ -259,6 +259,30 @@
     return thisString.@replaceUsingStringSearch(searchString, replace);
 }
 
+function replaceAll(search, replace)
+{
+    "use strict";
+
+    if (@isUndefinedOrNull(this))
+        @throwTypeError("String.prototype.replaceAll requires |this| not to be null nor undefined");
+
+    if (search != null) {
+        if (@isRegExp(search) && !@stringIncludesInternal.@call(@toString(search.flags), "g"))
+            @throwTypeError("String.prototype.replaceAll argument must not be a non-global regular _expression_");
+
+        var replacer = search.@replaceSymbol;
+        if (replacer !== @undefined) {
+            if (!@hasObservableSideEffectsForStringReplace(search, replacer))
+                return @toString(this).@replaceUsingRegExp(search, replace);
+            return replacer.@call(search, this, replace);
+        }
+    }
+
+    var thisString = @toString(this);
+    var searchString = @toString(search);
+    return thisString.@replaceAllUsingStringSearch(searchString, replace);
+}
+
 function search(regexp)
 {
     "use strict";

Modified: trunk/Source/_javascript_Core/runtime/StringPrototype.cpp (252682 => 252683)


--- trunk/Source/_javascript_Core/runtime/StringPrototype.cpp	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/Source/_javascript_Core/runtime/StringPrototype.cpp	2019-11-20 05:12:14 UTC (rev 252683)
@@ -70,6 +70,7 @@
 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(JSGlobalObject*, CallFrame*);
+EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceAllUsingStringSearch(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(JSGlobalObject*, CallFrame*);
@@ -97,28 +98,29 @@
 
 /* Source for StringConstructor.lut.h
 @begin stringPrototypeTable
-    concat    JSBuiltin    DontEnum|Function 1
-    match     JSBuiltin    DontEnum|Function 1
-    matchAll  JSBuiltin    DontEnum|Function 1
-    padStart  JSBuiltin    DontEnum|Function 1
-    padEnd    JSBuiltin    DontEnum|Function 1
-    repeat    JSBuiltin    DontEnum|Function 1
-    replace   JSBuiltin    DontEnum|Function 2
-    search    JSBuiltin    DontEnum|Function 1
-    split     JSBuiltin    DontEnum|Function 1
-    anchor    JSBuiltin    DontEnum|Function 1
-    big       JSBuiltin    DontEnum|Function 0
-    bold      JSBuiltin    DontEnum|Function 0
-    blink     JSBuiltin    DontEnum|Function 0
-    fixed     JSBuiltin    DontEnum|Function 0
-    fontcolor JSBuiltin    DontEnum|Function 1
-    fontsize  JSBuiltin    DontEnum|Function 1
-    italics   JSBuiltin    DontEnum|Function 0
-    link      JSBuiltin    DontEnum|Function 1
-    small     JSBuiltin    DontEnum|Function 0
-    strike    JSBuiltin    DontEnum|Function 0
-    sub       JSBuiltin    DontEnum|Function 0
-    sup       JSBuiltin    DontEnum|Function 0
+    concat        JSBuiltin    DontEnum|Function 1
+    match         JSBuiltin    DontEnum|Function 1
+    matchAll      JSBuiltin    DontEnum|Function 1
+    padStart      JSBuiltin    DontEnum|Function 1
+    padEnd        JSBuiltin    DontEnum|Function 1
+    repeat        JSBuiltin    DontEnum|Function 1
+    replace       JSBuiltin    DontEnum|Function 2
+    replaceAll    JSBuiltin    DontEnum|Function 2
+    search        JSBuiltin    DontEnum|Function 1
+    split         JSBuiltin    DontEnum|Function 1
+    anchor        JSBuiltin    DontEnum|Function 1
+    big           JSBuiltin    DontEnum|Function 0
+    bold          JSBuiltin    DontEnum|Function 0
+    blink         JSBuiltin    DontEnum|Function 0
+    fixed         JSBuiltin    DontEnum|Function 0
+    fontcolor     JSBuiltin    DontEnum|Function 1
+    fontsize      JSBuiltin    DontEnum|Function 1
+    italics       JSBuiltin    DontEnum|Function 0
+    link          JSBuiltin    DontEnum|Function 1
+    small         JSBuiltin    DontEnum|Function 0
+    strike        JSBuiltin    DontEnum|Function 0
+    sub           JSBuiltin    DontEnum|Function 0
+    sup           JSBuiltin    DontEnum|Function 0
 @end
 */
 
@@ -142,6 +144,7 @@
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", stringProtoFuncLastIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingRegExpPrivateName(), stringProtoFuncReplaceUsingRegExp, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, StringPrototypeReplaceRegExpIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceUsingStringSearchPrivateName(), stringProtoFuncReplaceUsingStringSearch, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
+    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().replaceAllUsingStringSearchPrivateName(), stringProtoFuncReplaceAllUsingStringSearch, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, StringPrototypeSliceIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substr", stringProtoFuncSubstr, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substring", stringProtoFuncSubstring, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
@@ -394,6 +397,12 @@
         RELEASE_AND_RETURN(scope, jsString(vm, StringImpl::createSubstringSharingImpl(*source.impl(), std::max(0, position), std::min(sourceSize, length))));
     }
 
+    if (rangeCount == 2 && separatorCount == 1) {
+        String leftPart(StringImpl::createSubstringSharingImpl(*source.impl(), substringRanges[0].position, substringRanges[0].length));
+        String rightPart(StringImpl::createSubstringSharingImpl(*source.impl(), substringRanges[1].position, substringRanges[1].length));
+        RELEASE_AND_RETURN(scope, jsString(globalObject, leftPart, separators[0], rightPart));
+    }
+
     Checked<int, RecordOverflow> totalLength = 0;
     bool allSeparators8Bit = true;
     for (int i = 0; i < rangeCount; i++)
@@ -767,7 +776,7 @@
         vm, globalObject, callFrame, string, searchValue, callData, callType, replacementString, replaceValue));
 }
 
-static ALWAYS_INLINE JSString* replaceUsingStringSearch(VM& vm, JSGlobalObject* globalObject, JSString* jsString, JSValue searchValue, JSValue replaceValue)
+static ALWAYS_INLINE JSString* replaceUsingStringSearch(VM& vm, JSGlobalObject* globalObject, CallFrame* callFrame, JSString* jsString, JSValue searchValue, JSValue replaceValue, bool isGlobal)
 {
     auto scope = DECLARE_THROW_SCOPE(vm);
 
@@ -777,46 +786,66 @@
     RETURN_IF_EXCEPTION(scope, nullptr);
 
     size_t matchStart = string.find(searchString);
-
     if (matchStart == notFound)
         return jsString;
 
     CallData callData;
     CallType callType = getCallData(vm, replaceValue, callData);
-    if (callType != CallType::None) {
-        MarkedArgumentBuffer args;
-        auto* substring = jsSubstring(vm, string, matchStart, searchString.impl()->length());
+    Optional<CachedCall> cachedCall;
+    String replaceString;
+    if (callType == CallType::None) {
+        replaceString = replaceValue.toWTFString(globalObject);
         RETURN_IF_EXCEPTION(scope, nullptr);
-        args.append(substring);
-        args.append(jsNumber(matchStart));
-        args.append(jsString);
-        ASSERT(!args.hasOverflowed());
-        replaceValue = call(globalObject, replaceValue, callType, callData, jsUndefined(), args);
-        RETURN_IF_EXCEPTION(scope, nullptr);
+    } else {
+        cachedCall.emplace(globalObject, callFrame, jsCast<JSFunction*>(replaceValue), 3);
+        cachedCall->setThis(jsUndefined());
     }
 
-    String replaceString = replaceValue.toWTFString(globalObject);
-    RETURN_IF_EXCEPTION(scope, nullptr);
+    size_t endOfLastMatch = 0;
+    size_t searchStringLength = searchString.length();
+    Vector<StringRange, 16> sourceRanges;
+    Vector<String, 16> replacements;
+    do {
+        if (cachedCall) {
+            auto* substring = jsSubstring(vm, string, matchStart, searchStringLength);
+            RETURN_IF_EXCEPTION(scope, nullptr);
+            cachedCall->clearArguments();
+            cachedCall->appendArgument(substring);
+            cachedCall->appendArgument(jsNumber(matchStart));
+            cachedCall->appendArgument(jsString);
+            if (UNLIKELY(cachedCall->hasOverflowedArguments()))
+                OUT_OF_MEMORY(globalObject, scope);
+            JSValue replacement = cachedCall->call();
+            RETURN_IF_EXCEPTION(scope, nullptr);
+            replaceString = replacement.toWTFString(globalObject);
+            RETURN_IF_EXCEPTION(scope, nullptr);
+        }
 
-    StringImpl* stringImpl = string.impl();
-    String leftPart(StringImpl::createSubstringSharingImpl(*stringImpl, 0, matchStart));
-
-    size_t matchEnd = matchStart + searchString.impl()->length();
-    int ovector[2] = { static_cast<int>(matchStart),  static_cast<int>(matchEnd)};
-    String middlePart;
-    if (callType != CallType::None)
-        middlePart = replaceString;
-    else {
-        StringBuilder replacement(StringBuilder::OverflowHandler::RecordOverflow);
-        substituteBackreferences(replacement, replaceString, string, ovector, 0);
-        if (UNLIKELY(replacement.hasOverflowed()))
+        if (UNLIKELY(!sourceRanges.tryConstructAndAppend(endOfLastMatch, matchStart - endOfLastMatch)))
             OUT_OF_MEMORY(globalObject, scope);
-        middlePart = replacement.toString();
-    }
 
-    size_t leftLength = stringImpl->length() - matchEnd;
-    String rightPart(StringImpl::createSubstringSharingImpl(*stringImpl, matchEnd, leftLength));
-    RELEASE_AND_RETURN(scope, JSC::jsString(globalObject, leftPart, middlePart, rightPart));
+        size_t matchEnd = matchStart + searchStringLength;
+        int ovector[2] = { static_cast<int>(matchStart),  static_cast<int>(matchEnd)};
+        if (cachedCall) {
+            replacements.append(replaceString);
+            RETURN_IF_EXCEPTION(scope, nullptr);
+        } else {
+            StringBuilder replacement(StringBuilder::OverflowHandler::RecordOverflow);
+            substituteBackreferences(replacement, replaceString, string, ovector, nullptr);
+            if (UNLIKELY(replacement.hasOverflowed()))
+                OUT_OF_MEMORY(globalObject, scope);
+            replacements.append(replacement.toString());
+        }
+
+        endOfLastMatch = matchEnd;
+        if (!isGlobal)
+            break;
+        matchStart = string.find(searchString, UNLIKELY(!searchStringLength) ? endOfLastMatch + 1 : endOfLastMatch);
+    } while (matchStart != notFound);
+
+    if (UNLIKELY(!sourceRanges.tryConstructAndAppend(endOfLastMatch, string.length() - endOfLastMatch)))
+        OUT_OF_MEMORY(globalObject, scope);
+    RELEASE_AND_RETURN(scope, jsSpliceSubstringsWithSeparators(globalObject, jsString, string, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()));
 }
 
 static inline bool checkObjectCoercible(JSValue thisValue)
@@ -872,7 +901,8 @@
 {
     if (searchValue.inherits<RegExpObject>(vm))
         return replaceUsingRegExpSearch(vm, globalObject, callFrame, string, searchValue, replaceValue);
-    return replaceUsingStringSearch(vm, globalObject, string, searchValue, replaceValue);
+    constexpr bool isGlobal = false;
+    return replaceUsingStringSearch(vm, globalObject, callFrame, string, searchValue, replaceValue, isGlobal);
 }
 
 ALWAYS_INLINE JSString* replace(
@@ -912,9 +942,22 @@
     JSString* string = callFrame->thisValue().toString(globalObject);
     RETURN_IF_EXCEPTION(scope, encodedJSValue());
 
-    RELEASE_AND_RETURN(scope, JSValue::encode(replaceUsingStringSearch(vm, globalObject, string, callFrame->argument(0), callFrame->argument(1))));
+    constexpr bool isGlobal = false;
+    RELEASE_AND_RETURN(scope, JSValue::encode(replaceUsingStringSearch(vm, globalObject, callFrame, string, callFrame->argument(0), callFrame->argument(1), isGlobal)));
 }
 
+EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceAllUsingStringSearch(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSString* string = callFrame->thisValue().toString(globalObject);
+    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+    constexpr bool isGlobal = true;
+    RELEASE_AND_RETURN(scope, JSValue::encode(replaceUsingStringSearch(vm, globalObject, callFrame, string, callFrame->argument(0), callFrame->argument(1), isGlobal)));
+}
+
 JSCell* JIT_OPERATION operationStringProtoFuncReplaceGeneric(JSGlobalObject* globalObject, EncodedJSValue thisValue, EncodedJSValue searchValue, EncodedJSValue replaceValue)
 {
     VM& vm = globalObject->vm();

Modified: trunk/Source/WTF/ChangeLog (252682 => 252683)


--- trunk/Source/WTF/ChangeLog	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/Source/WTF/ChangeLog	2019-11-20 05:12:14 UTC (rev 252683)
@@ -1,3 +1,14 @@
+2019-11-19  Ross Kirsling  <ross.kirsl...@sony.com>
+
+        Implement String.prototype.replaceAll
+        https://bugs.webkit.org/show_bug.cgi?id=202471
+
+        Reviewed by Yusuke Suzuki.
+
+        * wtf/text/StringCommon.h:
+        (WTF::findCommon):
+        Fix logic: "start > length" early out should come before "empty search string" early out.
+
 2019-11-19  Yusuke Suzuki  <ysuz...@apple.com>
 
         [IndexedDB] IndexedDB's threading assertion should respect Web thread

Modified: trunk/Source/WTF/wtf/text/StringCommon.h (252682 => 252683)


--- trunk/Source/WTF/wtf/text/StringCommon.h	2019-11-20 04:31:20 UTC (rev 252682)
+++ trunk/Source/WTF/wtf/text/StringCommon.h	2019-11-20 05:12:14 UTC (rev 252683)
@@ -570,11 +570,12 @@
         return WTF::find(haystack.characters16(), haystack.length(), needle[0], start);
     }
 
+    if (start > haystack.length())
+        return notFound;
+
     if (!needleLength)
         return std::min(start, haystack.length());
 
-    if (start > haystack.length())
-        return notFound;
     unsigned searchLength = haystack.length() - start;
     if (needleLength > searchLength)
         return notFound;
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to