Diff
Modified: trunk/LayoutTests/ChangeLog (110232 => 110233)
--- trunk/LayoutTests/ChangeLog 2012-03-09 00:46:38 UTC (rev 110232)
+++ trunk/LayoutTests/ChangeLog 2012-03-09 00:49:31 UTC (rev 110233)
@@ -1,3 +1,20 @@
+2012-03-08 Gavin Barraclough <[email protected]>
+
+ String.prototype.match and replace do not clear global regexp lastIndex per ES5.1 15.5.4.10
+ https://bugs.webkit.org/show_bug.cgi?id=26890
+
+ Reviewed by Oliver Hunt.
+
+ Per 15.10.6.2 step 9.a.1 called via the action of the last iteration of 15.5.4.10 8.f.i.
+
+ * fast/js/script-tests/string-match.js: Added.
+ * fast/js/string-match-expected.txt: Added.
+ * fast/js/string-match.html: Added.
+ - Added test case for match.
+ * fast/js/script-tests/string-replace-2.js:
+ * fast/js/string-replace-2-expected.txt:
+ - Added tests for update correct lastIndex handling.
+
2012-03-08 Andy Estes <[email protected]>
NULL renderer possible in WebCore::HTMLInputElement::setCanReceiveDroppedFiles()
Added: trunk/LayoutTests/fast/js/script-tests/string-match.js (0 => 110233)
--- trunk/LayoutTests/fast/js/script-tests/string-match.js (rev 0)
+++ trunk/LayoutTests/fast/js/script-tests/string-match.js 2012-03-09 00:49:31 UTC (rev 110233)
@@ -0,0 +1,24 @@
+description(
+"String.match(…) test"
+);
+
+// match with a global regexp should set lastIndex to zero; if read-only this should throw.
+// If the regexp is not global, lastIndex is not modified.
+var re;
+function testMatch(_re, readonly)
+{
+ re = _re;
+ re.lastIndex = 3;
+ if (readonly)
+ re = Object.defineProperty(re, 'lastIndex', {writable:false});
+ return '0x1x2'.match(re);
+}
+
+shouldBe("testMatch(/x/g, false)", '["x","x"]');
+shouldThrow("testMatch(/x/g, true)");
+shouldBe("testMatch(/x/, false)", '["x"]');
+shouldBe("testMatch(/x/, true)", '["x"]');
+shouldBe("testMatch(/x/g, false); re.lastIndex", '0');
+shouldThrow("testMatch(/x/g, true); re.lastIndex");
+shouldBe("testMatch(/x/, false); re.lastIndex", '3');
+shouldBe("testMatch(/x/, true); re.lastIndex", '3');
Modified: trunk/LayoutTests/fast/js/script-tests/string-replace-2.js (110232 => 110233)
--- trunk/LayoutTests/fast/js/script-tests/string-replace-2.js 2012-03-09 00:46:38 UTC (rev 110232)
+++ trunk/LayoutTests/fast/js/script-tests/string-replace-2.js 2012-03-09 00:49:31 UTC (rev 110233)
@@ -18,3 +18,53 @@
shouldBe("testString.replace(/(.*)/g, function replaceWithDollars(matchGroup) { return '$1'; })", "\"$1$1\"");
shouldBe("testString.replace(/(.)(.*)/g, function replaceWithMultipleDollars(matchGroup) { return '$1$2'; })", "\"$1$2\"");
shouldBe("testString.replace(/(.)(.*)/, function checkReplacementArguments() { return arguments.length; })", "\"5\"");
+
+// replace with a global regexp should set lastIndex to zero; if read-only this should throw.
+// If the regexp is not global, lastIndex is not modified.
+var re;
+var replacer;
+function testReplace(_re, readonly)
+{
+ re = _re;
+ re.lastIndex = 3;
+ if (readonly)
+ re = Object.defineProperty(re, 'lastIndex', {writable:false});
+ return '0x1x2'.replace(re, replacer);
+}
+
+replacer = 'y';
+shouldBe("testReplace(/x/g, false)", '"0y1y2"');
+shouldThrow("testReplace(/x/g, true)");
+shouldBe("testReplace(/x/, false)", '"0y1x2"');
+shouldBe("testReplace(/x/, true)", '"0y1x2"');
+shouldBe("testReplace(/x/g, false); re.lastIndex", '0');
+shouldThrow("testReplace(/x/g, true); re.lastIndex");
+shouldBe("testReplace(/x/, false); re.lastIndex", '3');
+shouldBe("testReplace(/x/, true); re.lastIndex", '3');
+
+replacer = function() { return 'y'; };
+shouldBe("testReplace(/x/g, false)", '"0y1y2"');
+shouldThrow("testReplace(/x/g, true)");
+shouldBe("testReplace(/x/, false)", '"0y1x2"');
+shouldBe("testReplace(/x/, true)", '"0y1x2"');
+shouldBe("testReplace(/x/g, false); re.lastIndex", '0');
+shouldThrow("testReplace(/x/g, true); re.lastIndex");
+shouldBe("testReplace(/x/, false); re.lastIndex", '3');
+shouldBe("testReplace(/x/, true); re.lastIndex", '3');
+
+replacer = function() { "use strict"; return ++re.lastIndex; };
+shouldBe("testReplace(/x/g, false)", '"01122"');
+shouldThrow("testReplace(/x/g, true)");
+shouldBe("testReplace(/x/, false)", '"041x2"');
+shouldThrow("testReplace(/x/, true)");
+shouldBe("testReplace(/x/g, false); re.lastIndex", '2');
+shouldThrow("testReplace(/x/g, true); re.lastIndex");
+shouldBe("testReplace(/x/, false); re.lastIndex", '4');
+shouldThrow("testReplace(/x/, true); re.lastIndex");
+
+var replacerCalled = false;
+replacer = function() { replacerCalled = true; };
+shouldBeTrue("try { testReplace(/x/g, false); throw 0; } catch (e) { }; replacerCalled;");
+var replacerCalled = false;
+replacer = function() { replacerCalled = true; };
+shouldBeFalse("try { testReplace(/x/g, true); throw 0; } catch (e) { }; replacerCalled;");
Added: trunk/LayoutTests/fast/js/string-match-expected.txt (0 => 110233)
--- trunk/LayoutTests/fast/js/string-match-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/js/string-match-expected.txt 2012-03-09 00:49:31 UTC (rev 110233)
@@ -0,0 +1,17 @@
+String.match(…) test
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS testMatch(/x/g, false) is ["x","x"]
+PASS testMatch(/x/g, true) threw exception TypeError: Attempted to assign to readonly property..
+PASS testMatch(/x/, false) is ["x"]
+PASS testMatch(/x/, true) is ["x"]
+PASS testMatch(/x/g, false); re.lastIndex is 0
+PASS testMatch(/x/g, true); re.lastIndex threw exception TypeError: Attempted to assign to readonly property..
+PASS testMatch(/x/, false); re.lastIndex is 3
+PASS testMatch(/x/, true); re.lastIndex is 3
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/js/string-match.html (0 => 110233)
--- trunk/LayoutTests/fast/js/string-match.html (rev 0)
+++ trunk/LayoutTests/fast/js/string-match.html 2012-03-09 00:49:31 UTC (rev 110233)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>
Modified: trunk/LayoutTests/fast/js/string-replace-2-expected.txt (110232 => 110233)
--- trunk/LayoutTests/fast/js/string-replace-2-expected.txt 2012-03-09 00:46:38 UTC (rev 110232)
+++ trunk/LayoutTests/fast/js/string-replace-2-expected.txt 2012-03-09 00:49:31 UTC (rev 110233)
@@ -12,6 +12,32 @@
PASS testString.replace(/(.*)/g, function replaceWithDollars(matchGroup) { return '$1'; }) is "$1$1"
PASS testString.replace(/(.)(.*)/g, function replaceWithMultipleDollars(matchGroup) { return '$1$2'; }) is "$1$2"
PASS testString.replace(/(.)(.*)/, function checkReplacementArguments() { return arguments.length; }) is "5"
+PASS testReplace(/x/g, false) is "0y1y2"
+PASS testReplace(/x/g, true) threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/, false) is "0y1x2"
+PASS testReplace(/x/, true) is "0y1x2"
+PASS testReplace(/x/g, false); re.lastIndex is 0
+PASS testReplace(/x/g, true); re.lastIndex threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/, false); re.lastIndex is 3
+PASS testReplace(/x/, true); re.lastIndex is 3
+PASS testReplace(/x/g, false) is "0y1y2"
+PASS testReplace(/x/g, true) threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/, false) is "0y1x2"
+PASS testReplace(/x/, true) is "0y1x2"
+PASS testReplace(/x/g, false); re.lastIndex is 0
+PASS testReplace(/x/g, true); re.lastIndex threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/, false); re.lastIndex is 3
+PASS testReplace(/x/, true); re.lastIndex is 3
+PASS testReplace(/x/g, false) is "01122"
+PASS testReplace(/x/g, true) threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/, false) is "041x2"
+PASS testReplace(/x/, true) threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/g, false); re.lastIndex is 2
+PASS testReplace(/x/g, true); re.lastIndex threw exception TypeError: Attempted to assign to readonly property..
+PASS testReplace(/x/, false); re.lastIndex is 4
+PASS testReplace(/x/, true); re.lastIndex threw exception TypeError: Attempted to assign to readonly property..
+PASS try { testReplace(/x/g, false); throw 0; } catch (e) { }; replacerCalled; is true
+PASS try { testReplace(/x/g, true); throw 0; } catch (e) { }; replacerCalled; is false
PASS successfullyParsed is true
TEST COMPLETE
Modified: trunk/Source/_javascript_Core/ChangeLog (110232 => 110233)
--- trunk/Source/_javascript_Core/ChangeLog 2012-03-09 00:46:38 UTC (rev 110232)
+++ trunk/Source/_javascript_Core/ChangeLog 2012-03-09 00:49:31 UTC (rev 110233)
@@ -1,3 +1,17 @@
+2012-03-08 Gavin Barraclough <[email protected]>
+
+ String.prototype.match and replace do not clear global regexp lastIndex per ES5.1 15.5.4.10
+ https://bugs.webkit.org/show_bug.cgi?id=26890
+
+ Reviewed by Oliver Hunt.
+
+ Per 15.10.6.2 step 9.a.1 called via the action of the last iteration of 15.5.4.10 8.f.i.
+
+ * runtime/StringPrototype.cpp:
+ (JSC::replaceUsingRegExpSearch):
+ (JSC::stringProtoFuncMatch):
+ - added calls to setLastIndex.
+
2012-03-07 Jon Lee <[email protected]>
Add support for ENABLE(LEGACY_NOTIFICATIONS)
Modified: trunk/Source/_javascript_Core/runtime/StringPrototype.cpp (110232 => 110233)
--- trunk/Source/_javascript_Core/runtime/StringPrototype.cpp 2012-03-09 00:46:38 UTC (rev 110232)
+++ trunk/Source/_javascript_Core/runtime/StringPrototype.cpp 2012-03-09 00:49:31 UTC (rev 110233)
@@ -455,12 +455,20 @@
unsigned sourceLen = source.length();
if (exec->hadException())
return JSValue::encode(JSValue());
- RegExp* regExp = asRegExpObject(searchValue)->regExp();
+ RegExpObject* regExpObject = asRegExpObject(searchValue);
+ RegExp* regExp = regExpObject->regExp();
bool global = regExp->global();
- if (global && callType == CallTypeNone && !replacementString.length())
- return removeUsingRegExpSearch(exec, string, source, regExp);
+ if (global) {
+ // ES5.1 15.5.4.10 step 8.a.
+ regExpObject->setLastIndex(exec, 0);
+ if (exec->hadException())
+ return JSValue::encode(JSValue());
+ if (callType == CallTypeNone && !replacementString.length())
+ return removeUsingRegExpSearch(exec, string, source, regExp);
+ }
+
RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
int lastIndex = 0;
@@ -808,9 +816,17 @@
JSValue a0 = exec->argument(0);
RegExp* reg;
- if (a0.inherits(&RegExpObject::s_info))
- reg = asRegExpObject(a0)->regExp();
- else {
+ bool global = false;
+ if (a0.inherits(&RegExpObject::s_info)) {
+ RegExpObject* regExpObject = asRegExpObject(a0);
+ reg = regExpObject->regExp();
+ if ((global = reg->global())) {
+ // ES5.1 15.5.4.10 step 8.a.
+ regExpObject->setLastIndex(exec, 0);
+ if (exec->hadException())
+ return JSValue::encode(JSValue());
+ }
+ } else {
/*
* ECMA 15.5.4.12 String.prototype.search (regexp)
* If regexp is not an object whose [[Class]] property is "RegExp", it is
@@ -825,7 +841,7 @@
int pos;
int matchLength = 0;
regExpConstructor->performMatch(*globalData, reg, s, 0, pos, matchLength);
- if (!(reg->global())) {
+ if (!global) {
// case without 'g' flag is handled like RegExp.prototype.exec
if (pos < 0)
return JSValue::encode(jsNull());