summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/String/matchAll.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/non262/String/matchAll.js')
-rw-r--r--js/src/tests/non262/String/matchAll.js288
1 files changed, 288 insertions, 0 deletions
diff --git a/js/src/tests/non262/String/matchAll.js b/js/src/tests/non262/String/matchAll.js
new file mode 100644
index 0000000000..2752d8b688
--- /dev/null
+++ b/js/src/tests/non262/String/matchAll.js
@@ -0,0 +1,288 @@
+// Basic surface tests.
+
+assertEq(typeof String.prototype.matchAll, "function");
+assertEq(String.prototype.matchAll.name, "matchAll");
+assertEq(String.prototype.matchAll.length, 1);
+
+assertEq(typeof Symbol.matchAll, "symbol");
+
+assertEq(typeof RegExp.prototype[Symbol.matchAll], "function");
+assertEq(RegExp.prototype[Symbol.matchAll].name, "[Symbol.matchAll]");
+assertEq(RegExp.prototype[Symbol.matchAll].length, 1);
+
+const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
+const RegExpStringIteratorPrototype = Object.getPrototypeOf("".matchAll(""));
+
+assertEq(Object.getPrototypeOf(RegExpStringIteratorPrototype), IteratorPrototype);
+
+assertEq(typeof RegExpStringIteratorPrototype.next, "function");
+assertEq(RegExpStringIteratorPrototype.next.name, "next");
+assertEq(RegExpStringIteratorPrototype.next.length, 0);
+
+assertEq(RegExpStringIteratorPrototype[Symbol.toStringTag], "RegExp String Iterator");
+
+
+// Basic functional tests.
+
+const RegExp_prototype_exec = RegExp.prototype.exec;
+const RegExp_prototype_match = RegExp.prototype[Symbol.match];
+
+function assertEqIterMatchResult(actual, expected) {
+ assertEq(actual.done, expected.done);
+ if (actual.value === undefined || expected.value === undefined) {
+ assertEq(actual.value, expected.value);
+ } else {
+ assertEqArray(actual.value, expected.value);
+ assertEq(actual.value.input, expected.value.input);
+ assertEq(actual.value.index, expected.value.index);
+ }
+}
+
+function assertEqMatchResults(actual, expected) {
+ var actualIter = actual[Symbol.iterator]();
+ var expectedIter = expected[Symbol.iterator]();
+ while (true) {
+ var actualResult = actualIter.next();
+ var expectedResult = expectedIter.next();
+ assertEqIterMatchResult(actualResult, expectedResult);
+ if (actualResult.done && expectedResult.done)
+ return;
+ }
+}
+
+function* matchResults(string, regexp, lastIndex = 0) {
+ regexp.lastIndex = lastIndex;
+ while (true) {
+ var match = Reflect.apply(RegExp_prototype_exec, regexp, [string]);
+ if (match === null)
+ return;
+ yield match;
+ if (!regexp.global)
+ return;
+ }
+}
+
+assertEqMatchResults(/a/[Symbol.matchAll]("ababcca"), matchResults("ababcca", /a/));
+assertEqMatchResults("ababcca".matchAll(/a/g), matchResults("ababcca", /a/g));
+assertEqMatchResults("ababcca".matchAll("a"), matchResults("ababcca", /a/g));
+
+
+// Cross-compartment tests.
+
+{
+ let otherGlobal = newGlobal();
+
+ let iterator = otherGlobal.eval(`"ababcca".matchAll(/a/g)`);
+ let expected = matchResults("ababcca", /a/g);
+
+ assertEqIterMatchResult(RegExpStringIteratorPrototype.next.call(iterator),
+ expected.next());
+}
+
+
+// Optimization tests.
+//
+// The optimized path for MatchAllIterator reuses the input RegExp to avoid
+// extra RegExp allocations. To make this optimization undetectable from
+// user code, we need to ensure that:
+// 1. Modifications to the input RegExp through RegExp.prototype.compile are
+// detected and properly handled (= ignored).
+// 2. The RegExpStringIterator doesn't modify the input RegExp, for example
+// by updating the lastIndex property.
+// 3. Guards against modifications of built-in RegExp.prototype are installed.
+
+// Recompile RegExp (source) before first match.
+{
+ let regexp = /a+/g;
+ let iterator = "aabb".matchAll(regexp);
+
+ regexp.compile("b+", "g");
+ assertEqMatchResults(iterator, matchResults("aabb", /a+/g));
+}
+
+// Recompile RegExp (flags) before first match.
+{
+ let regexp = /a+/gi;
+ let iterator = "aAbb".matchAll(regexp);
+
+ regexp.compile("a+", "");
+ assertEqMatchResults(iterator, matchResults("aAbb", /a+/gi));
+}
+
+// Recompile RegExp (source) after first match.
+{
+ let regexp = /a+/g;
+ let iterator = "aabbaa".matchAll(regexp);
+ let expected = matchResults("aabbaa", /a+/g);
+
+ assertEqIterMatchResult(iterator.next(), expected.next());
+ regexp.compile("b+", "g");
+ assertEqIterMatchResult(iterator.next(), expected.next());
+}
+
+// Recompile RegExp (flags) after first match.
+{
+ let regexp = /a+/g;
+ let iterator = "aabbAA".matchAll(regexp);
+ let expected = matchResults("aabbAA", /a+/g);
+
+ assertEqIterMatchResult(iterator.next(), expected.next());
+ regexp.compile("a+", "i");
+ assertEqIterMatchResult(iterator.next(), expected.next());
+}
+
+// lastIndex property of input RegExp not modified when optimized path used.
+{
+ let regexp = /a+/g;
+ regexp.lastIndex = 1;
+ let iterator = "aabbaa".matchAll(regexp);
+ let expected = matchResults("aabbaa", /a+/g, 1);
+
+ assertEq(regexp.lastIndex, 1);
+ assertEqIterMatchResult(iterator.next(), expected.next());
+ assertEq(regexp.lastIndex, 1);
+ assertEqIterMatchResult(iterator.next(), expected.next());
+ assertEq(regexp.lastIndex, 1);
+}
+
+// Modifications to lastIndex property of input RegExp ignored when optimized path used.
+{
+ let regexp = /a+/g;
+ let iterator = "aabbaa".matchAll(regexp);
+ regexp.lastIndex = 1;
+ let expected = matchResults("aabbaa", /a+/g);
+
+ assertEq(regexp.lastIndex, 1);
+ assertEqIterMatchResult(iterator.next(), expected.next());
+ assertEq(regexp.lastIndex, 1);
+ assertEqIterMatchResult(iterator.next(), expected.next());
+ assertEq(regexp.lastIndex, 1);
+}
+
+// RegExp.prototype[Symbol.match] is modified to a getter, ensure this getter
+// is called exactly twice.
+try {
+ let regexp = /a+/g;
+
+ let callCount = 0;
+ Object.defineProperty(RegExp.prototype, Symbol.match, {
+ get() {
+ assertEq(this, regexp);
+ callCount++;
+ return RegExp_prototype_match;
+ }
+ });
+ let iterator = "aabbaa".matchAll(regexp);
+ assertEq(callCount, 2);
+} finally {
+ // Restore optimizable RegExp.prototype shape.
+ Object.defineProperty(RegExp.prototype, Symbol.match, {
+ value: RegExp_prototype_match,
+ writable: true, enumerable: false, configurable: true
+ });
+}
+
+// RegExp.prototype.exec is changed to a getter.
+try {
+ let regexp = /a+/g;
+ let iterator = "aabbaa".matchAll(regexp);
+ let lastIndices = [0, 2, 6][Symbol.iterator]();
+
+ let iteratorRegExp = null;
+ let callCount = 0;
+ Object.defineProperty(RegExp.prototype, "exec", {
+ get() {
+ callCount++;
+
+ if (iteratorRegExp === null)
+ iteratorRegExp = this;
+
+ assertEq(this === regexp, false);
+ assertEq(this, iteratorRegExp);
+ assertEq(this.source, regexp.source);
+ assertEq(this.flags, regexp.flags);
+ assertEq(this.lastIndex, lastIndices.next().value);
+ return RegExp_prototype_exec;
+ }
+ });
+
+ assertEqMatchResults(iterator, matchResults("aabbaa", /a+/g));
+ assertEq(callCount, 3);
+} finally {
+ // Restore optimizable RegExp.prototype shape.
+ Object.defineProperty(RegExp.prototype, "exec", {
+ value: RegExp_prototype_exec,
+ writable: true, enumerable: false, configurable: true
+ });
+}
+
+// RegExp.prototype.exec is changed to a value property.
+try {
+ let regexp = /a+/g;
+ let iterator = "aabbaa".matchAll(regexp);
+ let lastIndices = [0, 2, 6][Symbol.iterator]();
+
+ let iteratorRegExp = null;
+ let callCount = 0;
+ RegExp.prototype.exec = function(...args) {
+ callCount++;
+
+ if (iteratorRegExp === null)
+ iteratorRegExp = this;
+
+ assertEq(this === regexp, false);
+ assertEq(this, iteratorRegExp);
+ assertEq(this.source, regexp.source);
+ assertEq(this.flags, regexp.flags);
+ assertEq(this.lastIndex, lastIndices.next().value);
+ return Reflect.apply(RegExp_prototype_exec, this, args);
+ };
+
+ assertEqMatchResults(iterator, matchResults("aabbaa", /a+/g));
+ assertEq(callCount, 3);
+} finally {
+ // Restore optimizable RegExp.prototype shape.
+ Object.defineProperty(RegExp.prototype, "exec", {
+ value: RegExp_prototype_exec,
+ writable: true, enumerable: false, configurable: true
+ });
+}
+
+// Initial 'lastIndex' is zero if the RegExp is neither global nor sticky (1).
+{
+ let regexp = /a+/;
+ regexp.lastIndex = 2;
+
+ let iterator = regexp[Symbol.matchAll]("aaaaa");
+ assertEqMatchResults(iterator, matchResults("aaaaa", /a+/g, 0));
+}
+
+// Initial 'lastIndex' is zero if the RegExp is neither global nor sticky (2).
+{
+ let regexp = /a+/g;
+ regexp.lastIndex = 2;
+
+ let iterator = regexp[Symbol.matchAll]("aaaaa");
+ assertEqMatchResults(iterator, matchResults("aaaaa", /a+/g, 2));
+}
+
+// Initial 'lastIndex' is zero if the RegExp is neither global nor sticky (3).
+{
+ let regexp = /a+/y;
+ regexp.lastIndex = 2;
+
+ let iterator = regexp[Symbol.matchAll]("aaaaa");
+ assertEqMatchResults(iterator, matchResults("aaaaa", /a+/g, 2));
+}
+
+//Initial 'lastIndex' is zero if the RegExp is neither global nor sticky (4).
+{
+ let regexp = /a+/gy;
+ regexp.lastIndex = 2;
+
+ let iterator = regexp[Symbol.matchAll]("aaaaa");
+ assertEqMatchResults(iterator, matchResults("aaaaa", /a+/g, 2));
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);