From 5fd5b2ac2f396eb1d8707a691aa26ad159ad25e3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sat, 24 Mar 2018 16:01:06 +0100 Subject: Bug 1317397: Only set lastIndex for global or sticky RegExps in RegExpBuiltinExec per ES2017 --- js/src/builtin/RegExp.js | 80 ++++++++++---- js/src/builtin/RegExpLocalReplaceOpt.h.js | 12 +- js/src/builtin/Utilities.js | 12 +- .../jit-test/tests/basic/regexpLastIndexReset.js | 4 +- js/src/tests/ecma_2017/lastIndex-exec.js | 80 ++++++++++++++ .../tests/ecma_2017/lastIndex-match-or-replace.js | 122 +++++++++++++++++++++ js/src/tests/ecma_2017/lastIndex-search.js | 118 ++++++++++++++++++++ js/src/tests/ecma_3/String/15.5.4.11.js | 2 +- js/src/tests/ecma_5/RegExp/exec.js | 2 +- js/src/tests/ecma_6/RegExp/compile-lastIndex.js | 34 +++--- .../RegExp/match-local-tolength-recompilation.js | 4 +- .../RegExp/replace-local-tolength-recompilation.js | 4 +- js/src/tests/ecma_6/RegExp/search-trace.js | 2 + 13 files changed, 419 insertions(+), 57 deletions(-) create mode 100644 js/src/tests/ecma_2017/lastIndex-exec.js create mode 100644 js/src/tests/ecma_2017/lastIndex-match-or-replace.js create mode 100644 js/src/tests/ecma_2017/lastIndex-search.js (limited to 'js') diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js index f71ce4f75..0b849292c 100644 --- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -609,7 +609,8 @@ function RegExpGlobalReplaceShortOpt(rx, S, lengthS, replaceValue, fullUnicode) #undef SUBSTITUTION #undef FUNC_NAME -// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.9. +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 21.2.5.9 RegExp.prototype [ @@search ] ( string ) function RegExpSearch(string) { // Step 1. var rx = this; @@ -621,41 +622,69 @@ function RegExpSearch(string) { // Step 3. var S = ToString(string); + // Step 4. + var previousLastIndex = rx.lastIndex; + + // Step 5. + var lastIndexIsZero = SameValue(previousLastIndex, 0); + if (!lastIndexIsZero) + rx.lastIndex = 0; + if (IsRegExpMethodOptimizable(rx) && S.length < 0x7fff) { // Step 6. var result = RegExpSearcher(rx, S, 0); - // Step 8. + // We need to consider two cases: + // + // 1. Neither global nor sticky is set: + // RegExpBuiltinExec doesn't modify lastIndex for local RegExps, that + // means |SameValue(rx.lastIndex, 0)| is true after calling exec. The + // comparison in steps 7-8 |SameValue(rx.lastIndex, previousLastIndex)| + // is therefore equal to the already computed |lastIndexIsZero| value. + // + // 2. Global or sticky flag is set. + // RegExpBuiltinExec will always update lastIndex and we need to + // restore the property to its original value. + + // Steps 7-8. + if (!lastIndexIsZero) { + rx.lastIndex = previousLastIndex; + } else { + var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); + if (flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)) + rx.lastIndex = previousLastIndex; + } + + // Step 9. if (result === -1) return -1; - // Step 9. + // Step 10. return result & 0x7fff; } - return RegExpSearchSlowPath(rx, S); + return RegExpSearchSlowPath(rx, S, previousLastIndex); } -// ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.9 -// steps 4-9. -function RegExpSearchSlowPath(rx, S) { - // Step 4. - var previousLastIndex = rx.lastIndex; - - // Step 5. - rx.lastIndex = 0; - +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 21.2.5.9 RegExp.prototype [ @@search ] ( string ) +// Steps 6-10. +function RegExpSearchSlowPath(rx, S, previousLastIndex) { // Step 6. var result = RegExpExec(rx, S, false); // Step 7. - rx.lastIndex = previousLastIndex; + var currentLastIndex = rx.lastIndex; // Step 8. + if (!SameValue(currentLastIndex, previousLastIndex)) + rx.lastIndex = previousLastIndex; + + // Step 9. if (result === null) return -1; - // Step 9. + // Step 10. return result.index; } @@ -904,15 +933,16 @@ function RegExpExec(R, S, forTest) { return forTest ? result !== null : result; } -// ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2. +// ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 +// 21.2.5.2.2 Runtime Semantics: RegExpBuiltinExec ( R, S ) function RegExpBuiltinExec(R, S, forTest) { - // ES6 21.2.5.2.1 step 6. + // 21.2.5.2.1 Runtime Semantics: RegExpExec, step 5. // This check is here for RegExpTest. RegExp_prototype_Exec does same // thing already. if (!IsRegExpObject(R)) return UnwrapAndCallRegExpBuiltinExec(R, S, forTest); - // Steps 1-2 (skipped). + // Steps 1-3 (skipped). // Step 4. var lastIndex = ToLength(R.lastIndex); @@ -927,9 +957,11 @@ function RegExpBuiltinExec(R, S, forTest) { if (!globalOrSticky) { lastIndex = 0; } else { + // Step 12.a. if (lastIndex > S.length) { - // Steps 12.a.i-ii, 12.c.i.1-2. - R.lastIndex = 0; + // Steps 12.a.i-ii. + if (globalOrSticky) + R.lastIndex = 0; return forTest ? false : null; } } @@ -939,7 +971,8 @@ function RegExpBuiltinExec(R, S, forTest) { var endIndex = RegExpTester(R, S, lastIndex); if (endIndex == -1) { // Steps 12.a.i-ii, 12.c.i.1-2. - R.lastIndex = 0; + if (globalOrSticky) + R.lastIndex = 0; return false; } @@ -953,8 +986,9 @@ function RegExpBuiltinExec(R, S, forTest) { // Steps 3, 9-25, except 12.a.i-ii, 12.c.i.1-2, 15. var result = RegExpMatcher(R, S, lastIndex); if (result === null) { - // Steps 12.a.i-ii, 12.c.i.1-2. - R.lastIndex = 0; + // Steps 12.a.i, 12.c.i. + if (globalOrSticky) + R.lastIndex = 0; } else { // Step 15. if (globalOrSticky) diff --git a/js/src/builtin/RegExpLocalReplaceOpt.h.js b/js/src/builtin/RegExpLocalReplaceOpt.h.js index 96451d8a0..1acd6a73a 100644 --- a/js/src/builtin/RegExpLocalReplaceOpt.h.js +++ b/js/src/builtin/RegExpLocalReplaceOpt.h.js @@ -11,7 +11,7 @@ // * FUNCTIONAL -- replaceValue is a function // * neither of above -- replaceValue is a string without "$" -// ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8 +// ES 2017 draft 6390c2f1b34b309895d31d8c0512eac8660a0210 21.2.5.8 // steps 11.a-16. // Optimized path for @@replace with the following conditions: // * global flag is false @@ -36,8 +36,10 @@ function FUNC_NAME(rx, S, lengthS, replaceValue if (globalOrSticky) { // 21.2.5.2.2 RegExpBuiltinExec, step 12.a. if (lastIndex > lengthS) { - // FIXME: Implement changes for bug 1317397. - rx.lastIndex = 0; + if (globalOrSticky) + rx.lastIndex = 0; + + // Steps 12-16. return S; } } else { @@ -51,8 +53,8 @@ function FUNC_NAME(rx, S, lengthS, replaceValue // Step 11.b. if (result === null) { // 21.2.5.2.2 RegExpBuiltinExec, steps 12.a.i, 12.c.i. - // FIXME: Implement changes for bug 1317397. - rx.lastIndex = 0; + if (globalOrSticky) + rx.lastIndex = 0; // Steps 12-16. return S; diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 2dece3801..3145b4d51 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -106,7 +106,17 @@ function ToLength(v) { return std_Math_min(v, 0x1fffffffffffff); } -/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */ +// ES2017 draft rev aebf014403a3e641fb1622aec47c40f051943527 +// 7.2.9 SameValue ( x, y ) +function SameValue(x, y) { + if (x === y) { + return (x !== 0) || (1 / x === 1 / y); + } + return (x !== x && y !== y); +} + +// ES2017 draft rev aebf014403a3e641fb1622aec47c40f051943527 +// 7.2.10 SameValueZero ( x, y ) function SameValueZero(x, y) { return x === y || (x !== x && y !== y); } diff --git a/js/src/jit-test/tests/basic/regexpLastIndexReset.js b/js/src/jit-test/tests/basic/regexpLastIndexReset.js index 2a54d8ef5..dbe3c3b76 100644 --- a/js/src/jit-test/tests/basic/regexpLastIndexReset.js +++ b/js/src/jit-test/tests/basic/regexpLastIndexReset.js @@ -7,7 +7,7 @@ function test() { pattern.lastIndex = 3; var result = pattern.exec(string); assertEq(result, null); - assertEq(pattern.lastIndex, 0); + assertEq(pattern.lastIndex, 3); } for (let i = 0; i < 10; i++) { @@ -18,7 +18,7 @@ function test2() { pattern.lastIndex = 3; var result = pattern.test(string); assertEq(result, false); - assertEq(pattern.lastIndex, 0); + assertEq(pattern.lastIndex, 3); } for (let i = 0; i < 10; i++) { diff --git a/js/src/tests/ecma_2017/lastIndex-exec.js b/js/src/tests/ecma_2017/lastIndex-exec.js new file mode 100644 index 000000000..f42facbe3 --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-exec.js @@ -0,0 +1,80 @@ +// RegExp.prototype.exec: Test lastIndex changes for ES2017. + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCases = [ + { regExp: /a/, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: 0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -0, input: "a", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: -0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -0, input: "b", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -1, input: "a", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: -1, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -1, input: "b", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: 100, input: "a", result: 100 }, + { regExp: /a/g, lastIndex: 100, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 100, input: "a", result: 0 }, +]; + +// Basic test. +for (let {regExp, lastIndex, input, result} of testCases) { + let re = new RegExp(regExp); + re.lastIndex = lastIndex; + re.exec(input); + assertEq(re.lastIndex, result); +} + +// Test when lastIndex is non-writable. +for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re.exec(input), TypeError); + } else { + re.exec(input); + } + assertEq(re.lastIndex, lastIndex); +} + +// Test when lastIndex is changed to non-writable as a side-effect. +for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + let called = false; + re.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + Object.defineProperty(re, "lastIndex", { value: 9000, writable: false }); + return lastIndex; + } + }; + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re.exec(input), TypeError); + } else { + re.exec(input); + } + assertEq(re.lastIndex, 9000); + assertEq(called, true); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_2017/lastIndex-match-or-replace.js b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js new file mode 100644 index 000000000..b0a8b537c --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js @@ -0,0 +1,122 @@ +// RegExp.prototype[Symbol.match, Symbol.replace]: Test lastIndex changes for ES2017. + +// RegExp-like class to test the RegExp method slow paths. +class DuckRegExp extends RegExp { + constructor(pattern, flags) { + return Object.create(DuckRegExp.prototype, { + regExp: { + value: new RegExp(pattern, flags) + }, + lastIndex: { + value: 0, writable: true, enumerable: false, configurable: false + } + }); + } + + exec(...args) { + this.regExp.lastIndex = this.lastIndex; + try { + return this.regExp.exec(...args); + } finally { + if (this.global || this.sticky) + this.lastIndex = this.regExp.lastIndex; + } + } + + get source() { return this.regExp.source; } + + get global() { return this.regExp.global; } + get ignoreCase() { return this.regExp.ignoreCase; } + get multiline() { return this.regExp.multiline; } + get sticky() { return this.regExp.sticky; } + get unicode() { return this.regExp.unicode; } +} + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCases = [ + { regExp: /a/, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -0, input: "a", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -0, input: "b", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -1, input: "a", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -1, input: "b", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: 100, input: "a", result: 100 }, + { regExp: /a/g, lastIndex: 100, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 100, input: "a", result: 0 }, +]; + +for (let method of [RegExp.prototype[Symbol.match], RegExp.prototype[Symbol.replace]]) { + for (let Constructor of [RegExp, DuckRegExp]) { + // Basic test. + for (let {regExp, lastIndex, input, result} of testCases) { + let re = new Constructor(regExp); + re.lastIndex = lastIndex; + Reflect.apply(method, re, [input]); + assertEq(re.lastIndex, result); + } + + // Test when lastIndex is non-writable. + for (let {regExp, lastIndex, input} of testCases) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError); + } else { + Reflect.apply(method, re, [input]); + } + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is changed to non-writable as a side-effect. + for (let {regExp, lastIndex, input, result} of testCases) { + let re = new Constructor(regExp); + let called = false; + re.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + Object.defineProperty(re, "lastIndex", { value: 9000, writable: false }); + return lastIndex; + } + }; + if (re.sticky) { + assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError); + assertEq(called, true); + assertEq(re.lastIndex, 9000); + } else if (re.global) { + Reflect.apply(method, re, [input]); + assertEq(called, false); + assertEq(re.lastIndex, result); + } else { + Reflect.apply(method, re, [input]); + assertEq(called, true); + assertEq(re.lastIndex, 9000); + } + } + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_2017/lastIndex-search.js b/js/src/tests/ecma_2017/lastIndex-search.js new file mode 100644 index 000000000..5953b3a88 --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-search.js @@ -0,0 +1,118 @@ +// RegExp.prototype[Symbol.search]: Test lastIndex changes for ES2017. + +// RegExp-like class to test the RegExp method slow paths. +class DuckRegExp extends RegExp { + constructor(pattern, flags) { + return Object.create(DuckRegExp.prototype, { + regExp: { + value: new RegExp(pattern, flags) + }, + lastIndex: { + value: 0, writable: true, enumerable: false, configurable: false + } + }); + } + + exec(...args) { + this.regExp.lastIndex = this.lastIndex; + try { + return this.regExp.exec(...args); + } finally { + if (this.global || this.sticky) + this.lastIndex = this.regExp.lastIndex; + } + } + + get source() { return this.regExp.source; } + + get global() { return this.regExp.global; } + get ignoreCase() { return this.regExp.ignoreCase; } + get multiline() { return this.regExp.multiline; } + get sticky() { return this.regExp.sticky; } + get unicode() { return this.regExp.unicode; } +} + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCasesNotPositiveZero = [ + { regExp: /a/, lastIndex: -1, input: "a" }, + { regExp: /a/g, lastIndex: -1, input: "a" }, + { regExp: /a/y, lastIndex: -1, input: "a" }, + + { regExp: /a/, lastIndex: 100, input: "a" }, + { regExp: /a/g, lastIndex: 100, input: "a" }, + { regExp: /a/y, lastIndex: 100, input: "a" }, + + { regExp: /a/, lastIndex: -1, input: "b" }, + { regExp: /a/g, lastIndex: -1, input: "b" }, + { regExp: /a/y, lastIndex: -1, input: "b" }, + + { regExp: /a/, lastIndex: -0, input: "a" }, + { regExp: /a/g, lastIndex: -0, input: "a" }, + { regExp: /a/y, lastIndex: -0, input: "a" }, + + { regExp: /a/, lastIndex: -0, input: "b" }, + { regExp: /a/g, lastIndex: -0, input: "b" }, + { regExp: /a/y, lastIndex: -0, input: "b" }, +]; + +const testCasesPositiveZero = [ + { regExp: /a/, lastIndex: 0, input: "a" }, + { regExp: /a/g, lastIndex: 0, input: "a" }, + { regExp: /a/y, lastIndex: 0, input: "a" }, + + { regExp: /a/, lastIndex: 0, input: "b" }, + { regExp: /a/g, lastIndex: 0, input: "b" }, + { regExp: /a/y, lastIndex: 0, input: "b" }, +]; + +const testCases = [...testCasesNotPositiveZero, ...testCasesPositiveZero]; + +for (let Constructor of [RegExp, DuckRegExp]) { + // Basic test. + for (let {regExp, lastIndex, input} of testCases) { + let re = new Constructor(regExp); + re.lastIndex = lastIndex; + re[Symbol.search](input); + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is non-writable and not positive zero. + for (let {regExp, lastIndex, input} of testCasesNotPositiveZero) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError); + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is non-writable and positive zero. + for (let {regExp, lastIndex, input} of testCasesPositiveZero) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError); + } else { + re[Symbol.search](input); + } + assertEq(re.lastIndex, lastIndex); + } + + // Test lastIndex isn't converted to a number. + for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + let badIndex = { + valueOf() { + assertEq(false, true); + } + }; + re.lastIndex = badIndex; + re[Symbol.search](input); + assertEq(re.lastIndex, badIndex); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_3/String/15.5.4.11.js b/js/src/tests/ecma_3/String/15.5.4.11.js index 0fd6caaf4..a5515286a 100644 --- a/js/src/tests/ecma_3/String/15.5.4.11.js +++ b/js/src/tests/ecma_3/String/15.5.4.11.js @@ -157,7 +157,7 @@ reportCompare( rex = /y/, rex.lastIndex = 1; reportCompare( - "xxx0", + "xxx1", "xxx".replace(rex, "y") + rex.lastIndex, "Section 25" ); diff --git a/js/src/tests/ecma_5/RegExp/exec.js b/js/src/tests/ecma_5/RegExp/exec.js index 411f348d9..4284b6e01 100644 --- a/js/src/tests/ecma_5/RegExp/exec.js +++ b/js/src/tests/ecma_5/RegExp/exec.js @@ -165,7 +165,7 @@ r = /abc/; r.lastIndex = -17; res = r.exec("cdefg"); assertEq(res, null); -assertEq(r.lastIndex, 0); +assertEq(r.lastIndex, -17); r = /abc/g; r.lastIndex = -42; diff --git a/js/src/tests/ecma_6/RegExp/compile-lastIndex.js b/js/src/tests/ecma_6/RegExp/compile-lastIndex.js index 80c820f43..5bd7e0b98 100644 --- a/js/src/tests/ecma_6/RegExp/compile-lastIndex.js +++ b/js/src/tests/ecma_6/RegExp/compile-lastIndex.js @@ -17,15 +17,12 @@ print(BUGNUMBER + ": " + summary); var regex = /foo/i; -// Aside from making .lastIndex non-writable, this has two incidental effects +// Aside from making .lastIndex non-writable, this has one incidental effect // ubiquitously tested through the remainder of this test: // // * RegExp.prototype.compile will do everything it ordinarily does, BUT it // will throw a TypeError when attempting to zero .lastIndex immediately // before succeeding overall. -// * RegExp.prototype.test for a non-global and non-sticky regular expression, -// in case of a match, will return true (as normal). BUT if no match is -// found, it will throw a TypeError when attempting to modify .lastIndex. // // Ain't it great? Object.defineProperty(regex, "lastIndex", { value: 42, writable: false }); @@ -40,8 +37,8 @@ assertEq(regex.lastIndex, 42); assertEq(regex.test("foo"), true); assertEq(regex.test("FOO"), true); -assertThrowsInstanceOf(() => regex.test("bar"), TypeError); -assertThrowsInstanceOf(() => regex.test("BAR"), TypeError); +assertEq(regex.test("bar"), false); +assertEq(regex.test("BAR"), false); assertThrowsInstanceOf(() => regex.compile("bar"), TypeError); @@ -52,10 +49,10 @@ assertEq(regex.unicode, false); assertEq(regex.sticky, false); assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false); assertEq(regex.lastIndex, 42); -assertThrowsInstanceOf(() => regex.test("foo"), TypeError); -assertThrowsInstanceOf(() => regex.test("FOO"), TypeError); +assertEq(regex.test("foo"), false); +assertEq(regex.test("FOO"), false); assertEq(regex.test("bar"), true); -assertThrowsInstanceOf(() => regex.test("BAR"), TypeError); +assertEq(regex.test("BAR"), false); assertThrowsInstanceOf(() => regex.compile("^baz", "m"), TypeError); @@ -66,19 +63,16 @@ assertEq(regex.unicode, false); assertEq(regex.sticky, false); assertEq(Object.getOwnPropertyDescriptor(regex, "lastIndex").writable, false); assertEq(regex.lastIndex, 42); -assertThrowsInstanceOf(() => regex.test("foo"), TypeError); -assertThrowsInstanceOf(() => regex.test("FOO"), TypeError); -assertThrowsInstanceOf(() => regex.test("bar"), TypeError); -assertThrowsInstanceOf(() => regex.test("BAR"), TypeError); +assertEq(regex.test("foo"), false); +assertEq(regex.test("FOO"), false); +assertEq(regex.test("bar"), false); +assertEq(regex.test("BAR"), false); assertEq(regex.test("baz"), true); -assertThrowsInstanceOf(() => regex.test("BAZ"), TypeError); -assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901baz"), - TypeError); +assertEq(regex.test("BAZ"), false); +assertEq(regex.test("012345678901234567890123456789012345678901baz"), false); assertEq(regex.test("012345678901234567890123456789012345678901\nbaz"), true); -assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901BAZ"), - TypeError); -assertThrowsInstanceOf(() => regex.test("012345678901234567890123456789012345678901\nBAZ"), - TypeError); +assertEq(regex.test("012345678901234567890123456789012345678901BAZ"), false); +assertEq(regex.test("012345678901234567890123456789012345678901\nBAZ"), false); /******************************************************************************/ diff --git a/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js b/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js index 6cb286b8c..9a992f81f 100644 --- a/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js +++ b/js/src/tests/ecma_6/RegExp/match-local-tolength-recompilation.js @@ -55,7 +55,7 @@ regExp.lastIndex = { } }; regExp[Symbol.match]("a"); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9001| after fixing 1317397"); +assertEq(regExp.lastIndex, 9001); // Case 3.b: Removes sticky flag without match, validate by checking lastIndex. var regExp = new RegExp("a", "y"); @@ -69,7 +69,7 @@ regExp.lastIndex = { } }; regExp[Symbol.match]("a"); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9002| after fixing 1317397"); +assertEq(regExp.lastIndex, 9002); if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js b/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js index 30002c3ed..e03177286 100644 --- a/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js +++ b/js/src/tests/ecma_6/RegExp/replace-local-tolength-recompilation.js @@ -55,7 +55,7 @@ regExp.lastIndex = { } }; regExp[Symbol.replace]("a", ""); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9001| after fixing 1317397"); +assertEq(regExp.lastIndex, 9001); // Case 3.b: Removes sticky flag without match, validate by checking lastIndex. var regExp = new RegExp("a", "y"); @@ -69,7 +69,7 @@ regExp.lastIndex = { } }; regExp[Symbol.replace]("a", ""); -assertEq(regExp.lastIndex, 0, "Update the expected value to |9002| after fixing 1317397"); +assertEq(regExp.lastIndex, 9002); if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/search-trace.js b/js/src/tests/ecma_6/RegExp/search-trace.js index ef14514c6..fc6bee754 100644 --- a/js/src/tests/ecma_6/RegExp/search-trace.js +++ b/js/src/tests/ecma_6/RegExp/search-trace.js @@ -56,6 +56,7 @@ assertEq(log, "get:lastIndex," + "set:lastIndex," + "get:exec,call:exec," + + "get:lastIndex," + "set:lastIndex," + "get:result[index],"); @@ -70,6 +71,7 @@ assertEq(log, "get:lastIndex," + "set:lastIndex," + "get:exec,call:exec," + + "get:lastIndex," + "set:lastIndex,"); if (typeof reportCompare === "function") -- cgit v1.2.3