Title: [259029] trunk
Revision
259029
Author
shvaikal...@gmail.com
Date
2020-03-25 19:24:29 -0700 (Wed, 25 Mar 2020)

Log Message

RegExp.prototype[@@replace] relies on globals and doesn't perform ToLength
https://bugs.webkit.org/show_bug.cgi?id=173867

Reviewed by Ross Kirsling.

JSTests:

* test262/expectations.yaml: Mark 4 test cases as passing.

Source/_javascript_Core:

This change:

a) Adds "lastIndex" ToLength coercion [1], which is observable, unlike ToLength coercion
   of RegExpExec result [2] that we omit, just like the one in @@split [3].

b) Removes `lastPosition` checks/updates, as there are none in the spec, and it was
   equivalent to checking `nextSourcePosition`.

c) Removes reliance of @@replace on globals and also replaces @stringSubstrInternal
   built-in with @stringSubstringInternal, as the former is Annex B and accepts size
   as 2nd paramter, which is not very handy because ECMA-262 usually says "substring
   of S consisting of the code units at indices X (inclusive) through Y (exclusive)".

[1]: https://tc39.es/ecma262/#sec-regexp.prototype-@@replace (step 11.c.iii.2.a)
[2]: https://tc39.es/ecma262/#sec-regexp.prototype-@@replace (step 14.a)
[3]: https://tc39.es/ecma262/#sec-regexp.prototype-@@split (step 19.d.iv.6)

* builtins/BuiltinNames.h:
* builtins/RegExpPrototype.js:
(getSubstitution):
(Symbol.replace):
(Symbol.split):
* builtins/StringPrototype.js:
(globalPrivate.repeatCharactersSlowPath):
* bytecode/LinkTimeConstant.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
* runtime/StringPrototype.cpp:
(JSC::stringIndexOfImpl):
(JSC::stringProtoFuncIndexOf):
(JSC::builtinStringIndexOfInternal):
(JSC::stringProtoFuncSubstr):
(JSC::stringSubstringImpl):
(JSC::stringProtoFuncSubstring):
(JSC::builtinStringSubstringInternal):
(JSC::stringProtoFuncSubstrImpl): Deleted.
(JSC::builtinStringSubstrInternal): Deleted.
* runtime/StringPrototype.h:

Modified Paths

Diff

Modified: trunk/JSTests/ChangeLog (259028 => 259029)


--- trunk/JSTests/ChangeLog	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/JSTests/ChangeLog	2020-03-26 02:24:29 UTC (rev 259029)
@@ -1,5 +1,14 @@
 2020-03-25  Alexey Shvayka  <shvaikal...@gmail.com>
 
+        RegExp.prototype[@@replace] relies on globals and doesn't perform ToLength
+        https://bugs.webkit.org/show_bug.cgi?id=173867
+
+        Reviewed by Ross Kirsling.
+
+        * test262/expectations.yaml: Mark 4 test cases as passing.
+
+2020-03-25  Alexey Shvayka  <shvaikal...@gmail.com>
+
         Invalid numeric and named references should be early syntax errors
         https://bugs.webkit.org/show_bug.cgi?id=178175
 

Modified: trunk/JSTests/test262/expectations.yaml (259028 => 259029)


--- trunk/JSTests/test262/expectations.yaml	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/JSTests/test262/expectations.yaml	2020-03-26 02:24:29 UTC (rev 259029)
@@ -1654,12 +1654,6 @@
 test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js:
   default: 'Test262Error: Expected SameValue(«�», «null») to be true'
   strict mode: 'Test262Error: Expected SameValue(«�», «null») to be true'
-test/built-ins/RegExp/prototype/Symbol.replace/coerce-lastindex.js:
-  default: 'Test262Error: Expected SameValue(«18014398509481984», «9007199254740992») to be true'
-  strict mode: 'Test262Error: Expected SameValue(«18014398509481984», «9007199254740992») to be true'
-test/built-ins/RegExp/prototype/Symbol.replace/poisoned-stdlib.js:
-  default: 'TypeError: undefined is not a function'
-  strict mode: 'TypeError: undefined is not a function'
 test/built-ins/RegExp/prototype/Symbol.search/set-lastindex-init-samevalue.js:
   default: 'Test262Error: Expected SameValue(«0», «0») to be true'
   strict mode: 'Test262Error: Expected SameValue(«0», «0») to be true'

Modified: trunk/Source/_javascript_Core/ChangeLog (259028 => 259029)


--- trunk/Source/_javascript_Core/ChangeLog	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-03-26 02:24:29 UTC (rev 259029)
@@ -1,5 +1,51 @@
 2020-03-25  Alexey Shvayka  <shvaikal...@gmail.com>
 
+        RegExp.prototype[@@replace] relies on globals and doesn't perform ToLength
+        https://bugs.webkit.org/show_bug.cgi?id=173867
+
+        Reviewed by Ross Kirsling.
+
+        This change:
+
+        a) Adds "lastIndex" ToLength coercion [1], which is observable, unlike ToLength coercion
+           of RegExpExec result [2] that we omit, just like the one in @@split [3].
+
+        b) Removes `lastPosition` checks/updates, as there are none in the spec, and it was
+           equivalent to checking `nextSourcePosition`.
+
+        c) Removes reliance of @@replace on globals and also replaces @stringSubstrInternal
+           built-in with @stringSubstringInternal, as the former is Annex B and accepts size
+           as 2nd paramter, which is not very handy because ECMA-262 usually says "substring
+           of S consisting of the code units at indices X (inclusive) through Y (exclusive)".
+
+        [1]: https://tc39.es/ecma262/#sec-regexp.prototype-@@replace (step 11.c.iii.2.a)
+        [2]: https://tc39.es/ecma262/#sec-regexp.prototype-@@replace (step 14.a)
+        [3]: https://tc39.es/ecma262/#sec-regexp.prototype-@@split (step 19.d.iv.6)
+
+        * builtins/BuiltinNames.h:
+        * builtins/RegExpPrototype.js:
+        (getSubstitution):
+        (Symbol.replace):
+        (Symbol.split):
+        * builtins/StringPrototype.js:
+        (globalPrivate.repeatCharactersSlowPath):
+        * bytecode/LinkTimeConstant.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        * runtime/StringPrototype.cpp:
+        (JSC::stringIndexOfImpl):
+        (JSC::stringProtoFuncIndexOf):
+        (JSC::builtinStringIndexOfInternal):
+        (JSC::stringProtoFuncSubstr):
+        (JSC::stringSubstringImpl):
+        (JSC::stringProtoFuncSubstring):
+        (JSC::builtinStringSubstringInternal):
+        (JSC::stringProtoFuncSubstrImpl): Deleted.
+        (JSC::builtinStringSubstrInternal): Deleted.
+        * runtime/StringPrototype.h:
+
+2020-03-25  Alexey Shvayka  <shvaikal...@gmail.com>
+
         Invalid numeric and named references should be early syntax errors
         https://bugs.webkit.org/show_bug.cgi?id=178175
 

Modified: trunk/Source/_javascript_Core/builtins/BuiltinNames.h (259028 => 259029)


--- trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2020-03-26 02:24:29 UTC (rev 259029)
@@ -159,8 +159,9 @@
     macro(regExpStringIteratorUnicode) \
     macro(regExpStringIteratorDone) \
     macro(stringIncludesInternal) \
+    macro(stringIndexOfInternal) \
     macro(stringSplitFast) \
-    macro(stringSubstrInternal) \
+    macro(stringSubstringInternal) \
     macro(makeBoundFunction) \
     macro(hasOwnLengthProperty) \
     macro(importModule) \

Modified: trunk/Source/_javascript_Core/builtins/RegExpPrototype.js (259028 => 259029)


--- trunk/Source/_javascript_Core/builtins/RegExpPrototype.js	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/builtins/RegExpPrototype.js	2020-03-26 02:24:29 UTC (rev 259029)
@@ -191,14 +191,14 @@
         var result = "";
         var lastStart = 0;
 
-        for (var start = 0; start = replacement.indexOf("$", lastStart), start !== -1; lastStart = start) {
+        for (var start = 0; start = @stringIndexOfInternal.@call(replacement, "$", lastStart), start !== -1; lastStart = start) {
             if (start - lastStart > 0)
-                result = result + replacement.substring(lastStart, start);
+                result = result + @stringSubstringInternal.@call(replacement, lastStart, start);
             start++;
-            var ch = replacement.charAt(start);
-            if (ch === "")
+            if (start >= replacementLength)
                 result = result + "$";
             else {
+                var ch = replacement[start];
                 switch (ch)
                 {
                 case "$":
@@ -211,20 +211,20 @@
                     break;
                 case "`":
                     if (position > 0)
-                        result = result + str.substring(0, position);
+                        result = result + @stringSubstringInternal.@call(str, 0, position);
                     start++;
                     break;
                 case "'":
                     if (tailPos < stringLength)
-                        result = result + str.substring(tailPos);
+                        result = result + @stringSubstringInternal.@call(str, tailPos);
                     start++;
                     break;
                 case "<":
                     if (namedCaptures !== @undefined) {
                         var groupNameStartIndex = start + 1;
-                        var groupNameEndIndex = replacement.indexOf(">", groupNameStartIndex);
+                        var groupNameEndIndex = @stringIndexOfInternal.@call(replacement, ">", groupNameStartIndex);
                         if (groupNameEndIndex !== -1) {
-                            var groupName = replacement.substring(groupNameStartIndex, groupNameEndIndex);
+                            var groupName = @stringSubstringInternal.@call(replacement, groupNameStartIndex, groupNameEndIndex);
                             var capture = namedCaptures[groupName];
                             if (capture !== @undefined)
                                 result = result + @toString(capture);
@@ -238,7 +238,7 @@
                     start++;
                     break;
                 default:
-                    var chCode = ch.charCodeAt(0);
+                    var chCode = ch.@charCodeAt(0);
                     if (chCode >= 0x30 && chCode <= 0x39) {
                         var originalStart = start - 1;
                         start++;
@@ -245,12 +245,12 @@
 
                         var n = chCode - 0x30;
                         if (n > m) {
-                            result = result + replacement.substring(originalStart, start);
+                            result = result + @stringSubstringInternal.@call(replacement, originalStart, start);
                             break;
                         }
 
                         if (start < replacementLength) {
-                            var nextChCode = replacement.charCodeAt(start);
+                            var nextChCode = replacement.@charCodeAt(start);
                             if (nextChCode >= 0x30 && nextChCode <= 0x39) {
                                 var nn = 10 * n + nextChCode - 0x30;
                                 if (nn <= m) {
@@ -261,7 +261,7 @@
                         }
 
                         if (n == 0) {
-                            result = result + replacement.substring(originalStart, start);
+                            result = result + @stringSubstringInternal.@call(replacement, originalStart, start);
                             break;
                         }
 
@@ -275,7 +275,7 @@
             }
         }
 
-        return result + replacement.substring(lastStart);
+        return result + @stringSubstringInternal.@call(replacement, lastStart);
     }
 
     if (!@isObject(this))
@@ -313,8 +313,10 @@
             else {
                 var matchStr = @toString(result[0]);
 
-                if (!matchStr.length)
-                    regexp.lastIndex = @advanceStringIndex(str, regexp.lastIndex, unicode);
+                if (!matchStr.length) {
+                    var thisIndex = @toLength(regexp.lastIndex);
+                    regexp.lastIndex = @advanceStringIndex(str, thisIndex, unicode);
+                }
             }
         }
     }
@@ -321,7 +323,6 @@
 
     var accumulatedResult = "";
     var nextSourcePosition = 0;
-    var lastPosition = 0;
 
     for (var i = 0, resultListLength = resultList.length; i < resultListLength; ++i) {
         var result = resultList[i];
@@ -346,7 +347,10 @@
         var namedCaptures = result.groups;
 
         if (functionalReplace) {
-            var replacerArgs = [ matched ].concat(captures);
+            var replacerArgs = [ matched ];
+            for (var j = 0; j < captures.length; j++)
+                replacerArgs.@push(captures[j]);
+
             replacerArgs.@push(position);
             replacerArgs.@push(str);
 
@@ -362,10 +366,9 @@
             replacement = getSubstitution(matched, str, position, captures, namedCaptures, replace);
         }
 
-        if (position >= nextSourcePosition && position >= lastPosition) {
-            accumulatedResult = accumulatedResult + str.substring(nextSourcePosition, position) + replacement;
+        if (position >= nextSourcePosition) {
+            accumulatedResult = accumulatedResult + @stringSubstringInternal.@call(str, nextSourcePosition, position) + replacement;
             nextSourcePosition = position + matchLength;
-            lastPosition = position;
         }
     }
 
@@ -372,7 +375,7 @@
     if (nextSourcePosition >= stringLength)
         return  accumulatedResult;
 
-    return accumulatedResult + str.substring(nextSourcePosition);
+    return accumulatedResult + @stringSubstringInternal.@call(str, nextSourcePosition);
 }
 
 // 21.2.5.9 RegExp.prototype[@@search] (string)
@@ -562,7 +565,7 @@
             // iv. Else e != p,
             else {
                 // 1. Let T be a String value equal to the substring of S consisting of the elements at indices p (inclusive) through q (exclusive).
-                var subStr = @stringSubstrInternal.@call(str, position, matchPosition - position);
+                var subStr = @stringSubstringInternal.@call(str, position, matchPosition);
                 // 2. Perform ! CreateDataProperty(A, ! ToString(lengthA), T).
                 // 3. Let lengthA be lengthA + 1.
                 @putByValDirect(result, result.length, subStr);
@@ -597,7 +600,7 @@
         }
     }
     // 20. Let T be a String value equal to the substring of S consisting of the elements at indices p (inclusive) through size (exclusive).
-    var remainingStr = @stringSubstrInternal.@call(str, position, size);
+    var remainingStr = @stringSubstringInternal.@call(str, position, size);
     // 21. Perform ! CreateDataProperty(A, ! ToString(lengthA), T).
     @putByValDirect(result, result.length, remainingStr);
     // 22. Return A.

Modified: trunk/Source/_javascript_Core/builtins/StringPrototype.js (259028 => 259029)


--- trunk/Source/_javascript_Core/builtins/StringPrototype.js	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/builtins/StringPrototype.js	2020-03-26 02:24:29 UTC (rev 259029)
@@ -113,7 +113,7 @@
         operand += operand;
     }
     if (remainingCharacters)
-        result += @stringSubstrInternal.@call(string, 0, remainingCharacters);
+        result += @stringSubstringInternal.@call(string, 0, remainingCharacters);
     return result;
 }
 

Modified: trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h (259028 => 259029)


--- trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h	2020-03-26 02:24:29 UTC (rev 259029)
@@ -90,8 +90,9 @@
     v(regExpPrototypeSymbolReplace, nullptr) \
     v(regExpTestFast, nullptr) \
     v(stringIncludesInternal, nullptr) \
+    v(stringIndexOfInternal, nullptr) \
     v(stringSplitFast, nullptr) \
-    v(stringSubstrInternal, nullptr) \
+    v(stringSubstringInternal, nullptr) \
     v(makeBoundFunction, nullptr) \
     v(hasOwnLengthProperty, nullptr) \
     v(dateTimeFormat, nullptr) \

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (259028 => 259029)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2020-03-26 02:24:29 UTC (rev 259029)
@@ -1142,11 +1142,14 @@
     m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::stringIncludesInternal)].initLater([] (const Initializer<JSCell>& init) {
             init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 1, String(), builtinStringIncludesInternal));
         });
+    m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::stringIndexOfInternal)].initLater([] (const Initializer<JSCell>& init) {
+            init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 1, String(), builtinStringIndexOfInternal));
+        });
     m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::stringSplitFast)].initLater([] (const Initializer<JSCell>& init) {
             init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 2, String(), stringProtoFuncSplitFast));
         });
-    m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::stringSubstrInternal)].initLater([] (const Initializer<JSCell>& init) {
-            init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 2, String(), builtinStringSubstrInternal));
+    m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::stringSubstringInternal)].initLater([] (const Initializer<JSCell>& init) {
+            init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 2, String(), builtinStringSubstringInternal));
         });
 
     // Function prototype helpers.

Modified: trunk/Source/_javascript_Core/runtime/StringPrototype.cpp (259028 => 259029)


--- trunk/Source/_javascript_Core/runtime/StringPrototype.cpp	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/runtime/StringPrototype.cpp	2020-03-26 02:24:29 UTC (rev 259029)
@@ -1076,7 +1076,7 @@
     return JSValue::encode(jsUndefined());
 }
 
-EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(JSGlobalObject* globalObject, CallFrame* callFrame)
+static EncodedJSValue stringIndexOfImpl(JSGlobalObject* globalObject, CallFrame* callFrame)
 {
     VM& vm = globalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
@@ -1123,6 +1123,19 @@
     return JSValue::encode(jsNumber(result));
 }
 
+EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    return stringIndexOfImpl(globalObject, callFrame);
+}
+
+EncodedJSValue JSC_HOST_CALL builtinStringIndexOfInternal(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    ASSERT(callFrame->thisValue().isString());
+    ASSERT(callFrame->argument(0).isString());
+    ASSERT(callFrame->argument(1).isNumber() || callFrame->argument(1).isUndefined());
+    return stringIndexOfImpl(globalObject, callFrame);
+}
+
 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(JSGlobalObject* globalObject, CallFrame* callFrame)
 {
     VM& vm = globalObject->vm();
@@ -1365,7 +1378,7 @@
     return JSValue::encode(result);
 }
 
-static EncodedJSValue stringProtoFuncSubstrImpl(JSGlobalObject* globalObject, CallFrame* callFrame)
+EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(JSGlobalObject* globalObject, CallFrame* callFrame)
 {
     VM& vm = globalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
@@ -1409,27 +1422,8 @@
     return JSValue::encode(jsSubstring(vm, uString, substringStart, substringLength));
 }
 
-EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstr(JSGlobalObject* globalObject, CallFrame* callFrame)
+static EncodedJSValue stringSubstringImpl(JSGlobalObject* globalObject, CallFrame* callFrame)
 {
-    return stringProtoFuncSubstrImpl(globalObject, callFrame);
-}
-
-EncodedJSValue JSC_HOST_CALL builtinStringSubstrInternal(JSGlobalObject* globalObject, CallFrame* callFrame)
-{
-    // @substrInternal should not have any observable side effects (e.g. it should not call
-    // GetMethod(..., @@toPrimitive) on the thisValue).
-
-    // It is ok to use the default stringProtoFuncSubstr as the implementation of
-    // @substrInternal because @substrInternal will only be called by builtins, which will
-    // guarantee that we only pass it a string thisValue. As a result, stringProtoFuncSubstr
-    // will not need to call toString() on the thisValue, and there will be no observable
-    // side-effects.
-    ASSERT(callFrame->thisValue().isString());
-    return stringProtoFuncSubstrImpl(globalObject, callFrame);
-}
-
-EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(JSGlobalObject* globalObject, CallFrame* callFrame)
-{
     VM& vm = globalObject->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
@@ -1472,6 +1466,19 @@
     RELEASE_AND_RETURN(scope, JSValue::encode(jsSubstring(globalObject, jsString, substringStart, substringLength)));
 }
 
+EncodedJSValue JSC_HOST_CALL stringProtoFuncSubstring(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    return stringSubstringImpl(globalObject, callFrame);
+}
+
+EncodedJSValue JSC_HOST_CALL builtinStringSubstringInternal(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    ASSERT(callFrame->thisValue().isString());
+    ASSERT(callFrame->argument(0).isNumber());
+    ASSERT(callFrame->argument(1).isNumber() || callFrame->argument(1).isUndefined());
+    return stringSubstringImpl(globalObject, callFrame);   
+}
+
 EncodedJSValue JSC_HOST_CALL stringProtoFuncToLowerCase(JSGlobalObject* globalObject, CallFrame* callFrame)
 {
     VM& vm = globalObject->vm();

Modified: trunk/Source/_javascript_Core/runtime/StringPrototype.h (259028 => 259029)


--- trunk/Source/_javascript_Core/runtime/StringPrototype.h	2020-03-26 02:10:38 UTC (rev 259028)
+++ trunk/Source/_javascript_Core/runtime/StringPrototype.h	2020-03-26 02:24:29 UTC (rev 259029)
@@ -60,7 +60,8 @@
 EncodedJSValue JSC_HOST_CALL stringProtoFuncRepeatCharacter(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncSplitFast(JSGlobalObject*, CallFrame*);
 
-EncodedJSValue JSC_HOST_CALL builtinStringSubstrInternal(JSGlobalObject*, CallFrame*);
+EncodedJSValue JSC_HOST_CALL builtinStringSubstringInternal(JSGlobalObject*, CallFrame*);
 EncodedJSValue JSC_HOST_CALL builtinStringIncludesInternal(JSGlobalObject*, CallFrame*);
+EncodedJSValue JSC_HOST_CALL builtinStringIndexOfInternal(JSGlobalObject*, CallFrame*);
 
 } // namespace JSC
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to