diff options
Diffstat (limited to '')
42 files changed, 810 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/regexp/CheckRegExpSyntax.js b/js/src/jit-test/tests/regexp/CheckRegExpSyntax.js new file mode 100644 index 0000000000..19471fdf50 --- /dev/null +++ b/js/src/jit-test/tests/regexp/CheckRegExpSyntax.js @@ -0,0 +1,16 @@ +// |jit-test| skip-if: !('oomTest' in this) + +load(libdir + "asserts.js"); + +assertEq(checkRegExpSyntax("correct[reg]exp"), undefined); +let err = checkRegExpSyntax("regex[withSyntaxError"); +assertEq(err instanceof SyntaxError, true); + +oomTest(() => checkRegExpSyntax("correct(re)gexp")) + +var checkReturnedSyntaxError = true; +oomTest(() => { + let err = checkRegExpSyntax("regex[withSyntaxError"); + if (!(err instanceof SyntaxError)) { checkReturnedSyntaxError = false; } +}) +assertEq(checkReturnedSyntaxError, true); diff --git a/js/src/jit-test/tests/regexp/RegExpExec-errors.js b/js/src/jit-test/tests/regexp/RegExpExec-errors.js new file mode 100644 index 0000000000..bee579859f --- /dev/null +++ b/js/src/jit-test/tests/regexp/RegExpExec-errors.js @@ -0,0 +1,6 @@ +load(libdir + "asserts.js"); + +assertErrorMessage(() => RegExp.prototype.test.call({}), TypeError, + /test method called on incompatible Object/); +assertErrorMessage(() => RegExp.prototype[Symbol.match].call([]), TypeError, + /\[Symbol\.match\] method called on incompatible Array/); diff --git a/js/src/jit-test/tests/regexp/atom-match-unicode-split-surrogate.js b/js/src/jit-test/tests/regexp/atom-match-unicode-split-surrogate.js new file mode 100644 index 0000000000..3c1909fcf2 --- /dev/null +++ b/js/src/jit-test/tests/regexp/atom-match-unicode-split-surrogate.js @@ -0,0 +1,25 @@ +function test(flags) { + // RegExp with a simple atom matcher. + // - Global flag to enable setting 'lastIndex'. + let s = "\u{10000}"; + let re = RegExp(s, flags + "g"); + + for (let i = 0; i < 200; ++i) { + // Set lastIndex in the middle of the surrogate pair. + re.lastIndex = 1; + + // |exec| will reset lastIndex to the start of the surrogate pair. + let r = re.exec(s); + + // Atom match should succeed. + assertEq(r[0], s); + assertEq(r.index, 0); + assertEq(re.lastIndex, 2); + } +} + +// Unicode flag to enable surrogate pairs support. +test("u"); + +// Unicode-Sets flag to enable surrogate pairs support. +test("v"); diff --git a/js/src/jit-test/tests/regexp/bug-1841771.js b/js/src/jit-test/tests/regexp/bug-1841771.js new file mode 100644 index 0000000000..ae13aa86f5 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug-1841771.js @@ -0,0 +1,76 @@ +var lfLogBuffer = ` +let hasFunction ; +for (const name of [, "" ]) + g55 = newGlobal(); +gcparam('maxBytes', gcparam('gcBytes') ); +//corefuzz-dcd-endofdata +/* +//corefuzz-dcd-endofdata +/**/ + + + +let hasFunction ; +for (const name of [ "", "", "", ""]) { + + + + + + const present = name in this; + if (!present) + thisname = function() {}; +} +//corefuzz-dcd-endofdata +//corefuzz-dcd-selectmode 1089924061 +//corefuzz-dcd-endofdata +oomTest(function() { + let m14 = parseModule('a'.replace(/a/, assertEq.toString)); +}); +`; +lfLogBuffer = lfLogBuffer.split('\n'); +let lfPreamble = ` + Object.defineProperty(this, "fuzzutils", { + value:{ + untemplate: function(s) { + return s.replace(/\\\\/g, '\\\\\\\\').replace(/\`/g, '\\\\\`').replace(/\\\$/g, '\\\\\$'); + } + } + }); +function lfFixRedeclaration(lfSrc, lfExc, lfRewriteSet) { + let varName; + let srcParts = lfSrc.split("\\n"); + let regReplace = new RegExp; + for (let lfIdx = 0; lfIdx < srcParts.length; ++lfIdx) + srcParts[lfExc.lineNumber - 1] = srcParts[lfExc.lineNumber - 1].replace(regReplace, varName); + return srcParts.join(); +} +`; +evaluate(lfPreamble); +let lfRunTypeId = -1; +let lfCodeBuffer = ""; +while (true) { + let line = lfLogBuffer.shift(); + if (line == null) break; + else if (line == "//corefuzz-dcd-endofdata") { + loadFile(lfCodeBuffer); + lfCodeBuffer = ""; + } else lfCodeBuffer += line + "\n"; +} +loadFile(lfCodeBuffer); +function loadFile(lfVarx, lfForceRunType = 0, lfPatchSets = []) { + try { + if (lfVarx.indexOf("//corefuzz-dcd-selectmode ") === 0) { + if (lfGCStress); + } else { + evaluate(lfVarx); + } + } catch (lfVare) { + if (lfVare.toString.indexOf0 >= 0); + else if (lfVare.toString().indexOf("redeclaration of ") >= 0) { + let lfNewSrc = lfFixRedeclaration(lfVarx, lfVare, lfPatchSets); + loadFile(lfNewSrc, lfRunTypeId, lfPatchSets); + } + lfVarx = fuzzutils.untemplate(lfVarx); + } +} diff --git a/js/src/jit-test/tests/regexp/bug-1845715.js b/js/src/jit-test/tests/regexp/bug-1845715.js new file mode 100644 index 0000000000..992a5a8d8a --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug-1845715.js @@ -0,0 +1,2 @@ +// |jit-test| skip-if: !('oomTest' in this) +oomTest(() => { gc(); /./d.exec(); }); diff --git a/js/src/jit-test/tests/regexp/bug1419785.js b/js/src/jit-test/tests/regexp/bug1419785.js new file mode 100644 index 0000000000..b40eca9f8c --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1419785.js @@ -0,0 +1,3 @@ +// |jit-test| error:character class +Object.defineProperty(RegExp.prototype, Symbol.search, {get: () => { throw "wrong"; }}); +"abc".search("[["); diff --git a/js/src/jit-test/tests/regexp/bug1445907.js b/js/src/jit-test/tests/regexp/bug1445907.js new file mode 100644 index 0000000000..a0e0dca57b --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1445907.js @@ -0,0 +1,14 @@ +// |jit-test| skip-if: !wasmDebuggingEnabled() + +// On ARM64, we failed to save x28 properly when generating code for the regexp +// matcher. +// +// There's wasm and Debugger code here because the combination forces the use of +// x28 and exposes the bug when running on the simulator. + +var g = newGlobal({newCompartment: true}); +var dbg = new Debugger(g); +g.eval(`var m = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func (export "test")))')))`); +var re = /./; +dbg.onEnterFrame = function(frame) { re.exec("x") }; +result = g.eval("m.exports.test()"); diff --git a/js/src/jit-test/tests/regexp/bug1600272.js b/js/src/jit-test/tests/regexp/bug1600272.js new file mode 100644 index 0000000000..08f154f771 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1600272.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +evalInWorker(` + Array.prototype[1] = 1; + var source = Array(50000).join("(") + "a" + Array(50000).join(")"); + var r70 = RegExp(source); +`); diff --git a/js/src/jit-test/tests/regexp/bug1640473.js b/js/src/jit-test/tests/regexp/bug1640473.js new file mode 100644 index 0000000000..d2ba97043d --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1640473.js @@ -0,0 +1,20 @@ +// |jit-test| skip-if: getBuildConfiguration("wasi") +var s = ""; +var input = ""; +for (var i = 0; i < 500; ++i) { + s += "(?<a" + i + ">a)"; + s += "(?<b" + i + ">b)?"; + input += "a"; +} + +try { + var r = RegExp(s); + var e = r.exec(input); + + for (var i = 0; i < 500; i++) { + assertEq(e.groups["a" + i], "a"); + assertEq(e.groups["b" + i], undefined); + } +} catch (err) { + assertEq(err.message, "too much recursion"); +} diff --git a/js/src/jit-test/tests/regexp/bug1640475.js b/js/src/jit-test/tests/regexp/bug1640475.js new file mode 100644 index 0000000000..58c092ec1d --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1640475.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var i = 0; +oomTest(function() { + for (var j = 0; j < 10; ++j) { + var r = RegExp(`(?<_${(i++).toString(32)}>a)`); + r.exec("a"); + } +}); diff --git a/js/src/jit-test/tests/regexp/bug1640479.js b/js/src/jit-test/tests/regexp/bug1640479.js new file mode 100644 index 0000000000..ff166d6451 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1640479.js @@ -0,0 +1,15 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var failures = 0; +var i = 0; + +function foo() { + var e; + var r = RegExp("(?<_" + (i++) + "a>)"); + try { e = r.exec("a"); } catch {} + e = r.exec("a"); + if (e.groups === undefined) failures++; +} + +oomTest(foo); +assertEq(failures, 0); diff --git a/js/src/jit-test/tests/regexp/bug1640487.js b/js/src/jit-test/tests/regexp/bug1640487.js new file mode 100644 index 0000000000..7ee3756075 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1640487.js @@ -0,0 +1,8 @@ +var re = /(?<x>a)|b/; + +function f(j) { + var s = String.fromCharCode(0x61 + (j === 1)); + var e = re.exec(s); + if (e.groups.x !== "a") print(i,j); +} +for (var i = 0; i < 2; ++i) f(i); diff --git a/js/src/jit-test/tests/regexp/bug1640592.js b/js/src/jit-test/tests/regexp/bug1640592.js new file mode 100644 index 0000000000..e00fd81e99 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1640592.js @@ -0,0 +1,2 @@ +gczeal(14, 16); +"a".match(/(?<a>a)/); diff --git a/js/src/jit-test/tests/regexp/bug1697077.js b/js/src/jit-test/tests/regexp/bug1697077.js new file mode 100644 index 0000000000..5551b98655 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1697077.js @@ -0,0 +1,13 @@ +// |jit-test| skip-if: !('interruptRegexp' in this) || getBuildConfiguration('pbl') +// skip testing under PBL because interruption of regex engine here seems to +// depend on GC behavior and is hard to reproduce reliably. +var s0 = "A".repeat(10*1024); +var interrupted = false; +gczeal(0); +setInterruptCallback(() => { + interrupted = true; + startgc(7,'shrinking'); + return true; +}); +assertEq(interruptRegexp(/a(bc|bd)/, s0), null); +assertEq(interrupted, true); diff --git a/js/src/jit-test/tests/regexp/bug1703750.js b/js/src/jit-test/tests/regexp/bug1703750.js new file mode 100644 index 0000000000..4be2d7748a --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1703750.js @@ -0,0 +1,7 @@ +function foo() { + const b = "".match(); + try { + foo(); + } catch {} +} +foo() diff --git a/js/src/jit-test/tests/regexp/bug1718842-1.js b/js/src/jit-test/tests/regexp/bug1718842-1.js new file mode 100644 index 0000000000..35b557b6b4 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1718842-1.js @@ -0,0 +1,7 @@ +var g = newGlobal({sameZoneAs: this}); + +var re1 = RegExp("(?<a>a)"); +var re2 = g.RegExp("(?<a>a)"); + +re1.exec("a"); +re2.exec("a"); diff --git a/js/src/jit-test/tests/regexp/bug1718842-2.js b/js/src/jit-test/tests/regexp/bug1718842-2.js new file mode 100644 index 0000000000..88f76046f7 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1718842-2.js @@ -0,0 +1,9 @@ +var g = newGlobal(); + +g.enableTrackAllocations(); + +var re1 = RegExp("(?<a>a)"); +var re2 = g.RegExp("(?<a>a)"); + +re1.exec("a"); +re2.exec("a"); diff --git a/js/src/jit-test/tests/regexp/bug1783555.js b/js/src/jit-test/tests/regexp/bug1783555.js new file mode 100644 index 0000000000..60978910a2 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1783555.js @@ -0,0 +1,4 @@ +var src = "(.?)".repeat(65536); +try { + "".match(src); +} catch {} diff --git a/js/src/jit-test/tests/regexp/bug1783830.js b/js/src/jit-test/tests/regexp/bug1783830.js new file mode 100644 index 0000000000..cd41be7f66 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1783830.js @@ -0,0 +1,7 @@ +assertEq(/[^!]/u.exec('\u{1F4A9}')[0], "\u{1F4A9}"); +assertEq(/^[^!]/u.exec('\u{1F4A9}')[0], "\u{1F4A9}"); +assertEq(/[^!]$/u.exec('\u{1F4A9}')[0], "\u{1F4A9}"); +assertEq(/![^!]/u.exec('!\u{1F4A9}')[0], "!\u{1F4A9}"); +assertEq(/[^!]!/u.exec('\u{1F4A9}!')[0], "\u{1F4A9}!"); +assertEq(/![^!]/ui.exec('!\u{1F4A9}')[0], "!\u{1F4A9}"); +assertEq(/[^!]!/ui.exec('\u{1F4A9}!')[0], "\u{1F4A9}!"); diff --git a/js/src/jit-test/tests/regexp/bug1786012.js b/js/src/jit-test/tests/regexp/bug1786012.js new file mode 100644 index 0000000000..f806fd22bb --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1786012.js @@ -0,0 +1,8 @@ +var repeated = "A".repeat(65536); +var src = "^(?:" + repeated + ")\$"; + +for (var i = 0; i < 100; i++) { + try { + RegExp(src).test(); + } catch {} +} diff --git a/js/src/jit-test/tests/regexp/bug1794317.js b/js/src/jit-test/tests/regexp/bug1794317.js new file mode 100644 index 0000000000..1ecb21eb64 --- /dev/null +++ b/js/src/jit-test/tests/regexp/bug1794317.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this) + +for (let i = 0; i < 2; i++) { + oomTest(function () { + RegExp("(?<name" + i + ">)").exec(); + }) +} diff --git a/js/src/jit-test/tests/regexp/builtin-exec-wrapper.js b/js/src/jit-test/tests/regexp/builtin-exec-wrapper.js new file mode 100644 index 0000000000..63660de643 --- /dev/null +++ b/js/src/jit-test/tests/regexp/builtin-exec-wrapper.js @@ -0,0 +1,7 @@ +var g = newGlobal({newCompartment: true}); +g.evaluate(`RegExp.prototype.exec = {};`); +var wrapper = g.evaluate(`/abc.+def/`); +assertEq(RegExp.prototype.test.call(wrapper, "abc"), false); +assertEq(RegExp.prototype.test.call(wrapper, "abcXdef"), true); +assertEq(RegExp.prototype[Symbol.match].call(wrapper, "abc"), null); +assertEq(RegExp.prototype[Symbol.match].call(wrapper, "abcXdef")[0], "abcXdef"); diff --git a/js/src/jit-test/tests/regexp/clone-statics.js b/js/src/jit-test/tests/regexp/clone-statics.js new file mode 100644 index 0000000000..d2f0ce1cac --- /dev/null +++ b/js/src/jit-test/tests/regexp/clone-statics.js @@ -0,0 +1,21 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +offThreadCompileToStencil(` + function foo(x, {}) { + do { + re = /erwe/; + if (x === 1) + re.x = 1; + else + re.x = "a"; + assertEq(re.x.length, (x === 1) ? undefined : 1); + } while (!inIon()); + } + + foo(0, 0); + RegExp.multiline = 1; + foo(1, 0); +`); + +var stencil = finishOffThreadStencil(); +evalStencil(stencil); diff --git a/js/src/jit-test/tests/regexp/flag-getters.js b/js/src/jit-test/tests/regexp/flag-getters.js new file mode 100644 index 0000000000..fbc1ad7274 --- /dev/null +++ b/js/src/jit-test/tests/regexp/flag-getters.js @@ -0,0 +1,61 @@ +// Test inlining for RegExp flag getters. + +function testGlobal() { + const xs = [/a/, /b/g]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i & 1]; + assertEq(x.global, !!(i & 1)); + } +} +for (let i = 0; i < 2; ++i) testGlobal(); + +function testIgnoreCase() { + const xs = [/a/, /b/i]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i & 1]; + assertEq(x.ignoreCase, !!(i & 1)); + } +} +for (let i = 0; i < 2; ++i) testIgnoreCase(); + +function testMultiline() { + const xs = [/a/, /b/m]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i & 1]; + assertEq(x.multiline, !!(i & 1)); + } +} +for (let i = 0; i < 2; ++i) testMultiline(); + +function testDotAll() { + const xs = [/a/, /b/s]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i & 1]; + assertEq(x.dotAll, !!(i & 1)); + } +} +for (let i = 0; i < 2; ++i) testDotAll(); + +function testUnicode() { + const xs = [/a/, /b/u]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i & 1]; + assertEq(x.unicode, !!(i & 1)); + } +} +for (let i = 0; i < 2; ++i) testUnicode(); + +function testSticky() { + const xs = [/a/, /b/y]; + + for (let i = 0; i < 200; ++i) { + let x = xs[i & 1]; + assertEq(x.sticky, !!(i & 1)); + } +} +for (let i = 0; i < 2; ++i) testSticky(); diff --git a/js/src/jit-test/tests/regexp/has-capture-groups-intrinsic.js b/js/src/jit-test/tests/regexp/has-capture-groups-intrinsic.js new file mode 100644 index 0000000000..2dfb450ea9 --- /dev/null +++ b/js/src/jit-test/tests/regexp/has-capture-groups-intrinsic.js @@ -0,0 +1,20 @@ +function test() { + var RegExpHasCaptureGroups = getSelfHostedValue("RegExpHasCaptureGroups"); + var cases = [ + [/a.+/, false], + [/abc/, false], + [/\r\n?|\n/, false], + [/(abc)/, true], + [/a(.+)/, true], + [/a(b)(c)(d)/, true], + [/a(?:b)/, false], + [/((?:a))/, true], + [/(?<name>a)/, true], + ]; + for (var i = 0; i < 10; i++) { + for (var [re, expected] of cases) { + assertEq(RegExpHasCaptureGroups(re, "abcdef"), expected); + } + } +} +test(); diff --git a/js/src/jit-test/tests/regexp/huge-01.js b/js/src/jit-test/tests/regexp/huge-01.js new file mode 100644 index 0000000000..bc1529f0c6 --- /dev/null +++ b/js/src/jit-test/tests/regexp/huge-01.js @@ -0,0 +1,21 @@ + +function g(N, p) { + var prefix = p.repeat(N); + var str = prefix + "[AB]"; + + try { + var re = new RegExp(str); + re.exec(prefix + "A"); + } catch(e) { + // 1. Regexp too big is raised by the lack of the 64k virtual registers + // reserved for the regexp evaluation. + // 2. The stack overflow can occur during the analysis of the regexp + assertEq(e.message.includes("regexp too big") || + e.message.includes("Stack overflow") || + e.message.includes("too much recursion"), true); + } +} + +var prefix = "/(?=k)ok/"; +for (var i = 0; i < 18; i++) + g(Math.pow(2, i), prefix) diff --git a/js/src/jit-test/tests/regexp/huge-02.js b/js/src/jit-test/tests/regexp/huge-02.js new file mode 100644 index 0000000000..1d0d94e10c --- /dev/null +++ b/js/src/jit-test/tests/regexp/huge-02.js @@ -0,0 +1,13 @@ +var interestingCaptureNums = [(1 << 14), + (1 << 15) - 1, + (1 << 15), + (1 << 15) + 1, + (1 << 16)] + +for (let i of interestingCaptureNums) { + print(i); + try { + var source = Array(i).join("(") + "a" + Array(i).join(")"); + RegExp(source).exec("a"); + } catch {} +} diff --git a/js/src/jit-test/tests/regexp/lastIndex-negative.js b/js/src/jit-test/tests/regexp/lastIndex-negative.js new file mode 100644 index 0000000000..536b3d2233 --- /dev/null +++ b/js/src/jit-test/tests/regexp/lastIndex-negative.js @@ -0,0 +1,14 @@ +// A negative lastIndex value must be treated as 0. +function test() { + var re = /abc.+de/g; + for (var i = 0; i < 100; i++) { + re.lastIndex = (i > 60) ? -1 : 0; + assertEq(typeof re.exec("abcXdef"), "object"); + assertEq(re.lastIndex, 6); + + re.lastIndex = (i > 60) ? -1 : 0; + assertEq(re.test("abcXdef"), true); + assertEq(re.lastIndex, 6); + } +} +test(); diff --git a/js/src/jit-test/tests/regexp/lastIndex-non-writable.js b/js/src/jit-test/tests/regexp/lastIndex-non-writable.js new file mode 100644 index 0000000000..74839762f5 --- /dev/null +++ b/js/src/jit-test/tests/regexp/lastIndex-non-writable.js @@ -0,0 +1,71 @@ +function testGlobalExec() { + var re = /abc.+de/g; + var c = 0; + for (var i = 0; i < 100; i++) { + re.lastIndex = 0; + if (i === 60) { + Object.freeze(re); + } + try { + re.exec("abcXdef"); + } catch (e) { + assertEq(e.toString().includes("lastIndex"), true); + c++; + } + assertEq(re.lastIndex, i >= 60 ? 0 : 6); + } + assertEq(c, 40); +} +testGlobalExec(); + +function testStickyTest() { + var re = /abc.+de/y; + var c = 0; + for (var i = 0; i < 100; i++) { + re.lastIndex = 0; + if (i === 60) { + Object.freeze(re); + } + try { + re.test("abcXdef"); + } catch (e) { + assertEq(e.toString().includes("lastIndex"), true); + c++; + } + assertEq(re.lastIndex, i >= 60 ? 0 : 6); + } + assertEq(c, 40); +} +testStickyTest(); + +// Must not reset too-large .lastIndex to 0 if non-writable. +function testLastIndexOutOfRange() { + var re = /abc.+de/g; + re.lastIndex = 123; + Object.freeze(re); + for (var i = 0; i < 100; i++) { + var ex = null; + try { + re.exec("abcXdef"); + } catch (e) { + ex = e; + } + assertEq(ex.toString().includes("lastIndex"), true); + assertEq(re.lastIndex, 123); + } +} +testLastIndexOutOfRange(); + +// .lastIndex is ignored for non-global/non-sticky regexps. +function testPlainExec() { + var re = /abc.+de/; + re.lastIndex = 1234; + for (var i = 0; i < 100; i++) { + if (i === 60) { + Object.freeze(re); + } + assertEq(re.exec("abcXdef")[0], "abcXde"); + assertEq(re.lastIndex, 1234); + } +} +testPlainExec(); diff --git a/js/src/jit-test/tests/regexp/lastIndex-too-large.js b/js/src/jit-test/tests/regexp/lastIndex-too-large.js new file mode 100644 index 0000000000..3e04042dd0 --- /dev/null +++ b/js/src/jit-test/tests/regexp/lastIndex-too-large.js @@ -0,0 +1,13 @@ +function test() { + var re = /abc.+de/y; + for (var i = 0; i < 100; i++) { + re.lastIndex = 10; + assertEq(re.exec("abcXdef"), null); + assertEq(re.lastIndex, 0); + + re.lastIndex = 10; + assertEq(re.test("abcXdef"), false); + assertEq(re.lastIndex, 0); + } +} +test(); diff --git a/js/src/jit-test/tests/regexp/lastIndex-valueOf.js b/js/src/jit-test/tests/regexp/lastIndex-valueOf.js new file mode 100644 index 0000000000..3794d20c2e --- /dev/null +++ b/js/src/jit-test/tests/regexp/lastIndex-valueOf.js @@ -0,0 +1,16 @@ +function test() { + var re = /abc.+de/g; + var c = 0; + var weird = {valueOf() { c++; return 0; }}; + for (var i = 0; i < 100; i++) { + re.lastIndex = (i > 60) ? weird : 0; + assertEq(typeof re.exec("abcXdef"), "object"); + assertEq(re.lastIndex, 6); + + re.lastIndex = (i > 60) ? weird : 0; + assertEq(re.test("abcXdef"), true); + assertEq(re.lastIndex, 6); + } + assertEq(c, 78); +} +test(); diff --git a/js/src/jit-test/tests/regexp/match-indices-dictionary.js b/js/src/jit-test/tests/regexp/match-indices-dictionary.js new file mode 100644 index 0000000000..14b7fd0630 --- /dev/null +++ b/js/src/jit-test/tests/regexp/match-indices-dictionary.js @@ -0,0 +1,24 @@ +// |jit-test| skip-if: getBuildConfiguration("wasi") +var s = ""; +var input = ""; +for (var i = 0; i < 500; ++i) { + s += "(?<a" + i + ">a)"; + s += "(?<b" + i + ">b)?"; + input += "a"; +} + +try { + var r = RegExp(s, "d"); + var e = r.exec(input); + + for (var i = 0; i < 500; i++) { + assertEq(e.groups["a" + i], "a"); + assertEq(e.groups["b" + i], undefined); + + assertEq(e.indices.groups["a" + i][0], i) + assertEq(e.indices.groups["a" + i][1], i + 1) + assertEq(e.indices.groups["b" + i], undefined) + } +} catch (err) { + assertEq(err.message, "too much recursion"); +} diff --git a/js/src/jit-test/tests/regexp/match-indices-warp.js b/js/src/jit-test/tests/regexp/match-indices-warp.js new file mode 100644 index 0000000000..634d368aee --- /dev/null +++ b/js/src/jit-test/tests/regexp/match-indices-warp.js @@ -0,0 +1,8 @@ +var re = /A*(B)A*/d; +for (var i = 0; i < 100; i++) { + var match = re.exec("xxxxxAAABAxxxxx"); + assertEq(match.indices[0][0], 5); + assertEq(match.indices[0][1], 10); + assertEq(match.indices[1][0], 8); + assertEq(match.indices[1][1], 9); +} diff --git a/js/src/jit-test/tests/regexp/match-stub-realms.js b/js/src/jit-test/tests/regexp/match-stub-realms.js new file mode 100644 index 0000000000..9cf082f015 --- /dev/null +++ b/js/src/jit-test/tests/regexp/match-stub-realms.js @@ -0,0 +1,19 @@ +// Ensure regexp match stub uses the correct realm for the match object and +// the regexp statics. +function test() { + var g1 = newGlobal({sameCompartmentAs: this}); + var g2 = newGlobal({sameCompartmentAs: this}); + g1.evaluate("function match(s) { return /(.)([\\d]+)/.exec(s); }"); + g2.evaluate("function match(s) { return /(.)([\\d]+)/.exec(s); }"); + for (var i = 0; i < 25; i++) { + var res1 = g1.match(`A${i}`); + var res2 = g2.match(`B${i}`); + assertEq(objectGlobal(res1), g1); + assertEq(objectGlobal(res2), g2); + assertEq(g1.RegExp.$1, "A"); + assertEq(g1.RegExp.$2, String(i)); + assertEq(g2.RegExp.$1, "B"); + assertEq(g2.RegExp.$2, String(i)); + } +} +test(); diff --git a/js/src/jit-test/tests/regexp/named-capture-proxy.js b/js/src/jit-test/tests/regexp/named-capture-proxy.js new file mode 100644 index 0000000000..e7cc933850 --- /dev/null +++ b/js/src/jit-test/tests/regexp/named-capture-proxy.js @@ -0,0 +1,21 @@ +var access_log = ""; +const handler = { + get: function(obj, prop) { + access_log += prop + ","; + return prop in obj ? obj[prop] : "<filled by proxy>"; + } +}; + +class ProxiedGroupRegExp extends RegExp { + exec(s) { + var result = super.exec(s); + if (result) { + result.groups = new Proxy(result.groups, handler); + } + return result; + } +} + +let re = new ProxiedGroupRegExp("(?<x>.)"); +assertEq("a".replace(re, "$<x> $<y>"), "a <filled by proxy>"); +assertEq(access_log, "x,y,") diff --git a/js/src/jit-test/tests/regexp/negated-set-expression.js b/js/src/jit-test/tests/regexp/negated-set-expression.js new file mode 100644 index 0000000000..70be08f680 --- /dev/null +++ b/js/src/jit-test/tests/regexp/negated-set-expression.js @@ -0,0 +1 @@ +assertEq("ab".match(/^(?:a[^a])+$/v)[0], "ab"); diff --git a/js/src/jit-test/tests/regexp/non-unicode-case-folding-backreference.js b/js/src/jit-test/tests/regexp/non-unicode-case-folding-backreference.js new file mode 100644 index 0000000000..bfea5f89a9 --- /dev/null +++ b/js/src/jit-test/tests/regexp/non-unicode-case-folding-backreference.js @@ -0,0 +1,67 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +// See https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch +function Canonicalize(ch) { + var u = ch.toUpperCase(); + if (u.length > 1) return ch; + var cu = u.charCodeAt(0); + if (ch.charCodeAt(0) >= 128 && cu < 128) return ch; + return cu; +} + +function TestEquivalenceClass(eclass) { + var backref = /(.)\1/i; + + for (var i = 0; i < eclass.length; i++) { + for (var j = 0; j < eclass.length; j++) { + if (i == j) continue; + var c1 = eclass[i]; + var c2 = eclass[j]; + var cc = c1 + c2; + var shouldMatch = Canonicalize(c1) === Canonicalize(c2); + + assertEq(backref.test(cc), shouldMatch); + } + } +} + +function TestAll() { + for (var eclass of equivalence_classes) { + TestEquivalenceClass(eclass); + } +} + +// Interesting case-folding equivalence classes (as determined by +// ICU's UnicodeSet::closeOver). A class is interesting if it contains +// more than two characters, or if it contains any characters in +// IgnoreSet or SpecialAddSet as defined in new-regexp/special-case.h. +var equivalence_classes = [ + '\u0041\u0061', // Aa (sanity check) + '\u004b\u006b\u212a', // KkK + '\u0053\u0073\u017f', // Ssſ + '\u00b5\u039c\u03bc', // µΜμ + '\u00c5\u00e5\u212b', // ÅåÅ + '\u00df\u1e9e', // ßẞ + '\u03a9\u03c9\u2126', // ΩωΩ + '\u0390\u1fd3', // ΐΐ + '\u0398\u03b8\u03d1\u03f4', // Θθϑϴ + '\u03b0\u1fe3', // ΰΰ + '\u1f80\u1f88', // ᾀᾈ + '\u1fb3\u1fbc', // ᾳᾼ + '\u1fc3\u1fcc', // ῃῌ + '\u1ff3\u1ffc', // ῳῼ + '\ufb05\ufb06', // ſtst + + // Everything below this line is a well-behaved case-folding + // equivalence class with more than two characters but only one + // canonical case-folded character + '\u01c4\u01c5\u01c6', '\u01c7\u01c8\u01c9', '\u01ca\u01cb\u01cc', + '\u01f1\u01f2\u01f3', '\u0345\u0399\u03b9\u1fbe', '\u0392\u03b2\u03d0', + '\u0395\u03b5\u03f5', '\u039a\u03ba\u03f0', '\u03a0\u03c0\u03d6', + '\u03a1\u03c1\u03f1', '\u03a3\u03c2\u03c3', '\u03a6\u03c6\u03d5', + '\u0412\u0432\u1c80', '\u0414\u0434\u1c81', '\u041e\u043e\u1c82', + '\u0421\u0441\u1c83', '\u0422\u0442\u1c84\u1c85', '\u042a\u044a\u1c86', + '\u0462\u0463\u1c87', '\u1c88\ua64a\ua64b', '\u1e60\u1e61\u1e9b' +]; + +TestAll(); diff --git a/js/src/jit-test/tests/regexp/non-unicode-case-folding.js b/js/src/jit-test/tests/regexp/non-unicode-case-folding.js new file mode 100644 index 0000000000..52a799238e --- /dev/null +++ b/js/src/jit-test/tests/regexp/non-unicode-case-folding.js @@ -0,0 +1,68 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +// See https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch +function Canonicalize(ch) { + var u = ch.toUpperCase(); + if (u.length > 1) return ch; + var cu = u.charCodeAt(0); + if (ch.charCodeAt(0) >= 128 && cu < 128) return ch; + return cu; +} + +function TestEquivalenceClass(eclass) { + for (var i = 0; i < eclass.length; i++) { + for (var j = 0; j < eclass.length; j++) { + if (i == j) continue; + var c1 = eclass[i]; + var c2 = eclass[j]; + var shouldMatch = Canonicalize(c1) === Canonicalize(c2); + + var re1 = new RegExp(c1, 'i'); + var re2 = new RegExp('[' + c1 + ']', 'i'); + + assertEq(re1.test(c2), shouldMatch); + assertEq(re2.test(c2), shouldMatch); + } + } +} + +function TestAll() { + for (var eclass of equivalence_classes) { + TestEquivalenceClass(eclass); + } +} + +// Interesting case-folding equivalence classes (as determined by +// ICU's UnicodeSet::closeOver). A class is interesting if it contains +// more than two characters, or if it contains any characters in +// IgnoreSet or SpecialAddSet as defined in new-regexp/special-case.h. +var equivalence_classes = [ + '\u0041\u0061', // Aa (sanity check) + '\u004b\u006b\u212a', // KkK + '\u0053\u0073\u017f', // Ssſ + '\u00b5\u039c\u03bc', // µΜμ + '\u00c5\u00e5\u212b', // ÅåÅ + '\u00df\u1e9e', // ßẞ + '\u03a9\u03c9\u2126', // ΩωΩ + '\u0390\u1fd3', // ΐΐ + '\u0398\u03b8\u03d1\u03f4', // Θθϑϴ + '\u03b0\u1fe3', // ΰΰ + '\u1f80\u1f88', // ᾀᾈ + '\u1fb3\u1fbc', // ᾳᾼ + '\u1fc3\u1fcc', // ῃῌ + '\u1ff3\u1ffc', // ῳῼ + '\ufb05\ufb06', // ſtst + + // Everything below this line is a well-behaved case-folding + // equivalence class with more than two characters but only one + // canonical case-folded character + '\u01c4\u01c5\u01c6', '\u01c7\u01c8\u01c9', '\u01ca\u01cb\u01cc', + '\u01f1\u01f2\u01f3', '\u0345\u0399\u03b9\u1fbe', '\u0392\u03b2\u03d0', + '\u0395\u03b5\u03f5', '\u039a\u03ba\u03f0', '\u03a0\u03c0\u03d6', + '\u03a1\u03c1\u03f1', '\u03a3\u03c2\u03c3', '\u03a6\u03c6\u03d5', + '\u0412\u0432\u1c80', '\u0414\u0434\u1c81', '\u041e\u043e\u1c82', + '\u0421\u0441\u1c83', '\u0422\u0442\u1c84\u1c85', '\u042a\u044a\u1c86', + '\u0462\u0463\u1c87', '\u1c88\ua64a\ua64b', '\u1e60\u1e61\u1e9b' +]; + +TestAll(); diff --git a/js/src/jit-test/tests/regexp/replace-exec.js b/js/src/jit-test/tests/regexp/replace-exec.js new file mode 100644 index 0000000000..b6488ec678 --- /dev/null +++ b/js/src/jit-test/tests/regexp/replace-exec.js @@ -0,0 +1,12 @@ +function test() { + var re = /abc.+de/; + var c = 0; + for (var i = 0; i < 100; i++) { + assertEq(re.test("abcXdef"), true); + if (i === 60) { + RegExp.prototype.exec = () => { c++; return {}; }; + } + } + assertEq(c, 39); +} +test(); diff --git a/js/src/jit-test/tests/regexp/replace-global-lambda.js b/js/src/jit-test/tests/regexp/replace-global-lambda.js new file mode 100644 index 0000000000..ca102c8d62 --- /dev/null +++ b/js/src/jit-test/tests/regexp/replace-global-lambda.js @@ -0,0 +1,56 @@ +"use strict"; + +function testNoCaptureGroups() { + var s = "a..bb.cba."; + for (var i = 0; i < 20; i++) { + var log = ""; + var res = s.replace(/a|b/g, function(...args) { + assertEq(this, undefined); + assertEq(args.length, 3); + assertEq(args[2], s); + log += "(" + args[0] + args[1] + ")"; + return "X"; + }); + assertEq(res, "X..XX.cXX."); + assertEq(log, "(a0)(b3)(b4)(b7)(a8)"); + } +} +testNoCaptureGroups(); + +function testCaptureGroups() { + var s = "a..bb.cba."; + for (var i = 0; i < 20; i++) { + var log = ""; + var res = s.replace(/(a)|(b)/g, function(...args) { + assertEq(this, undefined); + assertEq(args.length, 5); + assertEq(args[4], s); + log += "(" + args[0] + args[1] + args[2] + args[3] + ")"; + return "X"; + }); + assertEq(res, "X..XX.cXX."); + assertEq(log, "(aaundefined0)(bundefinedb3)(bundefinedb4)(bundefinedb7)(aaundefined8)"); + } +} +testCaptureGroups(); + +// Calling RegExp#compile in the callback has no effect, because |replace| will +// create a new RegExp object from the original source/flags if this happens. +function testCaptureGroupsChanging() { + var s = "a..bb.cba."; + for (var i = 0; i < 20; i++) { + var log = ""; + var re = /a|b/g; + var res = s.replace(re, function(...args) { + assertEq(this, undefined); + assertEq(args.length, 3); + assertEq(args[2], s); + log += "(" + args[0] + args[1] + ")"; + re.compile("(a)|(b)", "g"); + return "X"; + }); + assertEq(res, "X..XX.cXX."); + assertEq(log, "(a0)(b3)(b4)(b7)(a8)"); + } +} +testCaptureGroupsChanging(); diff --git a/js/src/jit-test/tests/regexp/rope-inputs.js b/js/src/jit-test/tests/regexp/rope-inputs.js new file mode 100644 index 0000000000..ca10fa3a4f --- /dev/null +++ b/js/src/jit-test/tests/regexp/rope-inputs.js @@ -0,0 +1,10 @@ +function test() { + var re = /abc(.+)z/; + for (var i = 0; i < 100; i++) { + assertEq(re.exec(newRope("abcdefghijklmnopqrstuvw", "xyz"))[1], "defghijklmnopqrstuvwxy"); + assertEq(re.test(newRope("abcdefghijklmnopqrstuvw", "xyz")), true); + assertEq(re[Symbol.search](newRope(".abcdefghijklmnopqrstuvw", "xyz")), 1); + assertEq(re[Symbol.match](newRope("abcdefghijklmnopqrstuvw", "xyz"))[1], "defghijklmnopqrstuvwxy"); + } +} +test(); diff --git a/js/src/jit-test/tests/regexp/unicode-back-reference.js b/js/src/jit-test/tests/regexp/unicode-back-reference.js new file mode 100644 index 0000000000..90eef410a5 --- /dev/null +++ b/js/src/jit-test/tests/regexp/unicode-back-reference.js @@ -0,0 +1,3 @@ +for (var t = 0; t < 10000; t++) { + (t + "\u1234").split(/(.)\1/i); +} |