Bug 887016 - Part 9: Implement RegExp.prototype[@@match] and call it from String.prototype.match. r=till
authorTooru Fujisawa <arai_a@mac.com>
Sat, 05 Sep 2015 21:55:06 +0900
changeset 292202 b5b06959919ad3a0150c7ca1dfe0de7d2d9df7e1
parent 292201 ac74c5fe92172d8dbb363eb713e1512570c51af1
child 292203 3a37c4b0e33804f0af4e3d05fdcd087e6b359f00
push id30152
push userkwierso@gmail.com
push dateThu, 07 Apr 2016 20:42:20 +0000
treeherdermozilla-central@06678484909c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs887016
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 887016 - Part 9: Implement RegExp.prototype[@@match] and call it from String.prototype.match. r=till
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.js
js/src/builtin/String.js
js/src/jit-test/tests/ion/testStringMatch.js
js/src/jit/Lowering.cpp
js/src/js.msg
js/src/jsstr.cpp
js/src/jsstr.h
js/src/tests/ecma_6/RegExp/RegExpExec-exec.js
js/src/tests/ecma_6/RegExp/RegExpExec-return.js
js/src/tests/ecma_6/RegExp/constructor-constructor.js
js/src/tests/ecma_6/RegExp/match-this.js
js/src/tests/ecma_6/RegExp/match-trace.js
js/src/tests/ecma_6/RegExp/match.js
js/src/tests/ecma_6/String/match.js
js/src/vm/GlobalObject.cpp
js/src/vm/SelfHosting.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -639,16 +639,17 @@ const JSPropertySpec js::regexp_properti
 const JSFunctionSpec js::regexp_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
 #endif
     JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
     JS_FN("compile",        regexp_compile,     2,0),
     JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1,0),
     JS_SELF_HOSTED_FN("test", "RegExpTest" ,    1,0),
+    JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1,0),
     JS_FS_END
 };
 
 #define STATIC_PAREN_GETTER_CODE(parenNum)                                      \
     if (!res->createParen(cx, parenNum, args.rval()))                           \
         return false;                                                           \
     if (args.rval().isUndefined())                                              \
         args.rval().setString(cx->runtime()->emptyString);                      \
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -51,16 +51,107 @@ function RegExpToString()
     // Steps 5-6.
     var flags = R.flags;
 
     // Step 7.
     return '/' + pattern + '/' + flags;
 }
 _SetCanonicalName(RegExpToString, "toString");
 
+// ES 2016 draft Mar 25, 2016 21.2.5.2.3.
+function AdvanceStringIndex(S, index) {
+    // Step 1.
+    assert(typeof S === "string", "Expected string as 1st argument");
+
+    // Step 2.
+    assert(index >= 0 && index <= MAX_NUMERIC_INDEX, "Expected integer as 2nd argument");
+
+    // Step 3 (skipped).
+
+    // Step 4 (skipped).
+
+    // Step 5.
+    var length = S.length;
+
+    // Step 6.
+    if (index + 1 >= length)
+        return index + 1;
+
+    // Step 7.
+    var first = callFunction(std_String_charCodeAt, S, index);
+
+    // Step 8.
+    if (first < 0xD800 || first > 0xDBFF)
+        return index + 1;
+
+    // Step 9.
+    var second = callFunction(std_String_charCodeAt, S, index + 1);
+
+    // Step 10.
+    if (second < 0xDC00 || second > 0xDFFF)
+        return index + 1;
+
+    // Step 11.
+    return index + 2;
+}
+
+// ES 2016 draft Mar 25, 2016 21.2.5.6.
+function RegExpMatch(string) {
+    // Step 1.
+    var rx = this;
+
+    // Step 2.
+    if (!IsObject(rx))
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
+
+    // Step 3.
+    var S = ToString(string);
+
+    // Steps 4-5.
+    if (!rx.global)
+        return RegExpExec(rx, S, false);
+
+    // Step 6.a.
+    var fullUnicode = !!rx.unicode;
+
+    // Step 6.b.
+    rx.lastIndex = 0;
+
+    // Step 6.c.
+    var A = [];
+
+    // Step 6.d.
+    var n = 0;
+
+    // Step 6.e.
+    while (true) {
+        // Step 6.e.i.
+        var result = RegExpExec(rx, S, false);
+
+        // Step 6.e.ii.
+        if (result === null)
+          return (n === 0) ? null : A;
+
+        // Step 6.e.iii.1.
+        var matchStr = ToString(result[0]);
+
+        // Step 6.e.iii.2.
+        _DefineDataProperty(A, n, matchStr);
+
+        // Step 6.e.iii.4.
+        if (matchStr === "") {
+            var lastIndex = ToLength(rx.lastIndex);
+            rx.lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+        }
+
+        // Step 6.e.iii.5.
+        n++;
+    }
+}
+
 // ES6 21.2.5.2.
 // NOTE: This is not RegExpExec (21.2.5.2.1).
 function RegExp_prototype_Exec(string) {
     // Steps 1-3.
     var R = this;
     if (!IsObject(R) || !IsRegExpObject(R))
         return callFunction(CallRegExpMethodIfWrapped, R, string, "RegExp_prototype_Exec");
 
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -1,14 +1,81 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*global intl_Collator: false, */
 
+function StringProtoHasNoMatch() {
+    var ObjectProto = GetBuiltinPrototype("Object");
+    var StringProto = GetBuiltinPrototype("String");
+    if (!ObjectHasPrototype(StringProto, ObjectProto))
+        return false;
+    return !(std_match in StringProto);
+}
+
+function IsStringMatchOptimizable() {
+    var RegExpProto = GetBuiltinPrototype("RegExp");
+    // If RegExpPrototypeOptimizable succeeds, `exec` and `@@match` are
+    // guaranteed to be data properties.
+    return RegExpPrototypeOptimizable(RegExpProto) &&
+           RegExpProto.exec === RegExp_prototype_Exec &&
+           RegExpProto[std_match] === RegExpMatch;
+}
+
+// ES 2016 draft Mar 25, 2016 21.1.3.11.
+function String_match(regexp) {
+    // Step 1.
+    RequireObjectCoercible(this);
+
+    // Step 2.
+    var isPatternString = (typeof regexp === "string");
+    if (!(isPatternString && StringProtoHasNoMatch()) && regexp !== undefined && regexp !== null) {
+        // Step 2.a.
+        var matcher = GetMethod(regexp, std_match);
+
+        // Step 2.b.
+        if (matcher !== undefined)
+            return callContentFunction(matcher, regexp, this);
+    }
+
+    // Step 3.
+    var S = ToString(this);
+
+    // FIXME: Non-standard flags argument (bug 1108382).
+    var flags = undefined;
+    if (arguments.length > 1) {
+        if (IsMatchFlagsArgumentEnabled())
+            flags = arguments[1];
+        WarnOnceAboutFlagsArgument();
+    } else {
+        if (isPatternString && IsStringMatchOptimizable()) {
+            var flatResult = FlatStringMatch(S, regexp);
+            if (flatResult !== undefined)
+                return flatResult;
+        }
+    }
+
+    // Step 4.
+    var rx = RegExpCreate(regexp, flags);
+
+    // Step 5 (optimized case).
+    if (IsStringMatchOptimizable() && !flags)
+        return RegExpMatcher(rx, S, 0, false);
+
+    // Step 5.
+    return callContentFunction(GetMethod(rx, std_match), rx, S);
+}
+
+function String_generic_match(thisValue, regexp) {
+    if (thisValue === undefined)
+        ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.match');
+    return callFunction(String_match, thisValue, regexp);
+}
+
 /**
  * A helper function implementing the logic for both String.prototype.padStart
  * and String.prototype.padEnd as described in ES7 Draft March 29, 2016
  */
 function String_pad(maxLength, fillString, padEnd=false) {
 
     // Steps 1-2.
     RequireObjectCoercible(this);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/testStringMatch.js
@@ -0,0 +1,119 @@
+setJitCompilerOption("ion.warmup.trigger", 4);
+
+function testBasic() {
+  var f = function() {
+    var result = "abc".match("b");
+    assertEq(result.length, 1);
+    assertEq(result.index, 1);
+    assertEq(result[0], "b");
+  };
+  for (var i = 0; i < 40; i++) {
+    f();
+  }
+}
+testBasic();
+
+function testMod(apply, unapply) {
+  var f = function(applied) {
+    var result = "abc".match("b");
+    assertEq(result.length, 1);
+    if (applied) {
+      assertEq(result[0], "mod");
+    } else {
+      assertEq(result.index, 1);
+      assertEq(result[0], "b");
+    }
+  };
+  var applied = false;
+  for (var i = 0; i < 120; i++) {
+    f(applied);
+    if (i == 40) {
+      apply();
+      applied = true;
+    }
+    if (i == 80) {
+      unapply();
+      applied = false;
+    }
+  }
+}
+testMod(() => {
+  String.prototype[Symbol.match] = () => ["mod"];
+}, () => {
+  delete String.prototype[Symbol.match];
+});
+testMod(() => {
+  Object.prototype[Symbol.match] = () => ["mod"];
+}, () => {
+  delete Object.prototype[Symbol.match];
+});
+
+testMod(() => {
+  Object.setPrototypeOf(String.prototype, {
+    [Symbol.match]: () => ["mod"]
+  });
+}, () => {
+  Object.setPrototypeOf(String.prototype, Object.prototype);
+});
+
+var orig_exec = RegExp.prototype.exec;
+testMod(() => {
+  RegExp.prototype.exec = () => ["mod"];
+}, () => {
+  RegExp.prototype.exec = orig_exec;
+});
+
+var orig_match = RegExp.prototype[Symbol.match];
+testMod(() => {
+  RegExp.prototype[Symbol.match] = () => ["mod"];
+}, () => {
+  RegExp.prototype[Symbol.match] = orig_match;
+});
+
+var observed = false;
+function testObserved(apply, unapply) {
+  var f = function(applied) {
+    observed = false;
+    var result = "abc".match("b."); // Use meta char to avoid flat match.
+    assertEq(result.length, 1);
+    assertEq(result.index, 1);
+    assertEq(result[0], "bc");
+    assertEq(observed, applied);
+  };
+  var applied = false;
+  for (var i = 0; i < 120; i++) {
+    f(applied);
+    if (i == 40) {
+      apply();
+      applied = true;
+    }
+    if (i == 80) {
+      unapply();
+      applied = false;
+    }
+  }
+}
+
+var orig_global = Object.getOwnPropertyDescriptor(RegExp.prototype, "global");
+testObserved(() => {
+  Object.defineProperty(RegExp.prototype, "global", {
+    get: function() {
+      observed = true;
+      return false;
+    }
+  });
+}, () => {
+  Object.defineProperty(RegExp.prototype, "global", orig_global);
+});
+
+var orig_sticky = Object.getOwnPropertyDescriptor(RegExp.prototype, "sticky");
+testObserved(() => {
+  Object.defineProperty(RegExp.prototype, "sticky", {
+    get: function() {
+      observed = true;
+      return false;
+    }
+  });
+}, () => {
+  Object.defineProperty(RegExp.prototype, "sticky", orig_sticky);
+});
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2185,17 +2185,16 @@ MustCloneRegExpForCall(MCall* call, uint
 
     JSFunction* target = call->getSingleTarget();
     if (!target || !target->isNative())
         return true;
 
     if (useIndex == MCall::IndexOfArgument(0) &&
         (target->native() == str_split ||
          target->native() == str_replace ||
-         target->native() == str_match ||
          target->native() == str_search))
     {
         return false;
     }
 
     return true;
 }
 
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -439,16 +439,17 @@ MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER,  1
 MSG_DEF(JSMSG_INVALID_OPTION_VALUE,    2, JSEXN_RANGEERR, "invalid value {1} for option {0}")
 MSG_DEF(JSMSG_INVALID_TIME_ZONE,       1, JSEXN_RANGEERR, "invalid time zone in DateTimeFormat(): {0}")
 MSG_DEF(JSMSG_UNDEFINED_CURRENCY,      0, JSEXN_TYPEERR, "undefined currency in NumberFormat() with currency style")
 
 // RegExp
 MSG_DEF(JSMSG_BACK_REF_OUT_OF_RANGE,   0, JSEXN_SYNTAXERR, "back reference out of range in regular expression")
 MSG_DEF(JSMSG_BAD_CLASS_RANGE,         0, JSEXN_SYNTAXERR, "invalid range in character class")
 MSG_DEF(JSMSG_ESCAPE_AT_END_OF_REGEXP, 0, JSEXN_SYNTAXERR, "\\ at end of pattern")
+MSG_DEF(JSMSG_EXEC_NOT_OBJORNULL,      0, JSEXN_TYPEERR, "RegExp exec method should return object or null")
 MSG_DEF(JSMSG_INVALID_DECIMAL_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid decimal escape in regular expression")
 MSG_DEF(JSMSG_INVALID_GROUP,           0, JSEXN_SYNTAXERR, "invalid regexp group")
 MSG_DEF(JSMSG_INVALID_IDENTITY_ESCAPE, 0, JSEXN_SYNTAXERR, "invalid identity escape in regular expression")
 MSG_DEF(JSMSG_INVALID_UNICODE_ESCAPE,  0, JSEXN_SYNTAXERR, "invalid unicode escape in regular expression")
 MSG_DEF(JSMSG_MISSING_PAREN,           0, JSEXN_SYNTAXERR, "unterminated parenthetical")
 MSG_DEF(JSMSG_NEWREGEXP_FLAGGED,       0, JSEXN_TYPEERR, "can't supply flags when constructing one RegExp from another")
 MSG_DEF(JSMSG_NOTHING_TO_REPEAT,       0, JSEXN_SYNTAXERR, "nothing to repeat")
 MSG_DEF(JSMSG_NUMBERS_OUT_OF_ORDER,    0, JSEXN_SYNTAXERR, "numbers out of order in {} quantifier.")
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2255,42 +2255,16 @@ class MOZ_STACK_CLASS StringRegExpGuard
 
   private:
     StringRegExpGuard(const StringRegExpGuard&) = delete;
     void operator=(const StringRegExpGuard&) = delete;
 };
 
 } /* anonymous namespace */
 
-static bool
-DoMatchLocal(JSContext* cx, const CallArgs& args, RegExpStatics* res, HandleLinearString input,
-             RegExpShared& re)
-{
-    ScopedMatchPairs matches(&cx->tempLifoAlloc());
-    bool sticky = re.sticky();
-    RegExpRunStatus status = re.execute(cx, input, 0, sticky, &matches, nullptr);
-    if (status == RegExpRunStatus_Error)
-        return false;
-
-    if (status == RegExpRunStatus_Success_NotFound) {
-        args.rval().setNull();
-        return true;
-    }
-
-    if (!res->updateFromMatchPairs(cx, input, matches))
-        return false;
-
-    RootedValue rval(cx);
-    if (!CreateRegExpMatchResult(cx, input, matches, &rval))
-        return false;
-
-    args.rval().set(rval);
-    return true;
-}
-
 /* ES6 21.2.5.2.3. */
 static size_t
 AdvanceStringIndex(HandleLinearString input, size_t length, size_t index, bool unicode)
 {
     /* Steps 1-3 (implicit). */
 
     /* Step 4: If input is latin1, there is no surrogate pair. */
     if (!unicode || input->hasLatin1Chars())
@@ -2316,209 +2290,16 @@ AdvanceStringIndex(HandleLinearString in
     /* Step 10. */
     if (!unicode::IsTrailSurrogate(second))
         return index + 1;
 
     /* Step 11. */
     return index + 2;
 }
 
-/* ES5 15.5.4.10 step 8. */
-static bool
-DoMatchGlobal(JSContext* cx, const CallArgs& args, RegExpStatics* res, HandleLinearString input,
-              StringRegExpGuard& g)
-{
-    // Step 8a.
-    //
-    // This single zeroing of "lastIndex" covers all "lastIndex" changes in the
-    // rest of String.prototype.match, particularly in steps 8f(i) and
-    // 8f(iii)(2)(a).  Here's why.
-    //
-    // The inputs to the calls to RegExp.prototype.exec are a RegExp object
-    // whose .global is true and a string.  The only side effect of a call in
-    // these circumstances is that the RegExp's .lastIndex will be modified to
-    // the next starting index after the discovered match (or to 0 if there's
-    // no remaining match).  Because .lastIndex is a non-configurable data
-    // property and no script-controllable code executes after step 8a, passing
-    // step 8a implies *every* .lastIndex set succeeds.  String.prototype.match
-    // calls RegExp.prototype.exec repeatedly, and the last call doesn't match,
-    // so the final value of .lastIndex is 0: exactly the state after step 8a
-    // succeeds.  No spec step lets script observe intermediate .lastIndex
-    // values.
-    //
-    // The arrays returned by RegExp.prototype.exec always have a string at
-    // index 0, for which [[Get]]s have no side effects.
-    //
-    // Filling in a new array using [[DefineOwnProperty]] is unobservable.
-    //
-    // This is a tricky point, because after this set, our implementation *can*
-    // fail.  The key is that script can't distinguish these failure modes from
-    // one where, in spec terms, we fail immediately after step 8a.  That *in
-    // reality* we might have done extra matching work, or created a partial
-    // results array to return, or hit an interrupt, is irrelevant.  The
-    // script can't tell we did any of those things but didn't update
-    // .lastIndex.  Thus we can optimize steps 8b onward however we want,
-    // including eliminating intermediate .lastIndex sets, as long as we don't
-    // add ways for script to observe the intermediate states.
-    //
-    // In short: it's okay to cheat (by setting .lastIndex to 0, once) because
-    // we can't get caught.
-    if (!g.zeroLastIndex(cx))
-        return false;
-
-    // Step 8b.
-    AutoValueVector elements(cx);
-
-    size_t lastSuccessfulStart = 0;
-
-    // The loop variables from steps 8c-e aren't needed, as we use different
-    // techniques from the spec to implement step 8f's loop.
-
-    // Step 8f.
-    ScopedMatchPairs matches(&cx->tempLifoAlloc());
-    size_t charsLen = input->length();
-    RegExpShared& re = g.regExp();
-    bool unicode = re.unicode();
-    bool sticky = re.sticky();
-    for (size_t searchIndex = 0; searchIndex <= charsLen; ) {
-        if (!CheckForInterrupt(cx))
-            return false;
-
-        // Steps 8f(i-ii), minus "lastIndex" updates (see above).
-        RegExpRunStatus status = re.execute(cx, input, searchIndex, sticky, &matches, nullptr);
-        if (status == RegExpRunStatus_Error)
-            return false;
-
-        // Step 8f(ii).
-        if (status == RegExpRunStatus_Success_NotFound)
-            break;
-
-        lastSuccessfulStart = searchIndex;
-        MatchPair& match = matches[0];
-
-        // Steps 8f(iii)(1-3).
-        searchIndex = match.isEmpty()
-                      ? AdvanceStringIndex(input, charsLen, match.limit, unicode)
-                      : match.limit;
-
-        // Step 8f(iii)(4-5).
-        JSLinearString* str = NewDependentString(cx, input, match.start, match.length());
-        if (!str)
-            return false;
-        if (!elements.append(StringValue(str)))
-            return false;
-    }
-
-    // Step 8g.
-    if (elements.empty()) {
-        args.rval().setNull();
-        return true;
-    }
-
-    // The last *successful* match updates the RegExpStatics. (Interestingly,
-    // this implies that String.prototype.match's semantics aren't those
-    // implied by the RegExp.prototype.exec calls in the ES5 algorithm.)
-    res->updateLazily(cx, input, &re, lastSuccessfulStart, sticky);
-
-    // Steps 8b, 8f(iii)(5-6), 8h.
-    JSObject* array = NewDenseCopiedArray(cx, elements.length(), elements.begin());
-    if (!array)
-        return false;
-
-    args.rval().setObject(*array);
-    return true;
-}
-
-static bool
-BuildFlatMatchArray(JSContext* cx, HandleString textstr, const FlatMatch& fm, CallArgs* args)
-{
-    if (fm.match() < 0) {
-        args->rval().setNull();
-        return true;
-    }
-
-    /* Get the templateObject that defines the shape and type of the output object */
-    JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
-    if (!templateObject)
-        return false;
-
-    RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, 1, templateObject));
-    if (!arr)
-        return false;
-
-    /* Store a Value for each pair. */
-    arr->setDenseInitializedLength(1);
-    arr->initDenseElement(0, StringValue(fm.pattern()));
-
-    /* Set the |index| property. (TemplateObject positions it in slot 0) */
-    arr->setSlot(0, Int32Value(fm.match()));
-
-    /* Set the |input| property. (TemplateObject positions it in slot 1) */
-    arr->setSlot(1, StringValue(textstr));
-
-#ifdef DEBUG
-    RootedValue test(cx);
-    RootedId id(cx, NameToId(cx->names().index));
-    if (!NativeGetProperty(cx, arr, id, &test))
-        return false;
-    MOZ_ASSERT(test == arr->getSlot(0));
-    id = NameToId(cx->names().input);
-    if (!NativeGetProperty(cx, arr, id, &test))
-        return false;
-    MOZ_ASSERT(test == arr->getSlot(1));
-#endif
-
-    args->rval().setObject(*arr);
-    return true;
-}
-
-/* ES5 15.5.4.10. */
-bool
-js::str_match(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    /* Steps 1-2. */
-    RootedString str(cx, ThisToStringForStringProto(cx, args));
-    if (!str)
-        return false;
-
-    /* Steps 3-4, plus the trailing-argument "flags" extension. */
-    StringRegExpGuard g(cx);
-    if (!g.init(cx, args, true))
-        return false;
-
-    /* Fast path when the search pattern can be searched for as a string. */
-    if (const FlatMatch* fm = g.tryFlatMatch(cx, str, 1, args.length()))
-        return BuildFlatMatchArray(cx, str, *fm, &args);
-
-    /* Return if there was an error in tryFlatMatch. */
-    if (cx->isExceptionPending())
-        return false;
-
-    /* Create regular-expression internals as needed to perform the match. */
-    if (!g.normalizeRegExp(cx, false, 1, args))
-        return false;
-
-    RegExpStatics* res = cx->global()->getRegExpStatics(cx);
-    if (!res)
-        return false;
-
-    RootedLinearString linearStr(cx, str->ensureLinear(cx));
-    if (!linearStr)
-        return false;
-
-    /* Steps 5-6, 7. */
-    if (!g.regExp().global())
-        return DoMatchLocal(cx, args, res, linearStr, g.regExp());
-
-    /* Steps 6, 8. */
-    return DoMatchGlobal(cx, args, res, linearStr, g);
-}
-
 bool
 js::str_search(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedString str(cx, ThisToStringForStringProto(cx, args));
     if (!str)
         return false;
 
@@ -4208,17 +3989,17 @@ static const JSFunctionSpec string_metho
     JS_FN("localeCompare",     str_localeCompare,     1,JSFUN_GENERIC_NATIVE),
 #endif
     JS_SELF_HOSTED_FN("repeat", "String_repeat",      1,0),
 #if EXPOSE_INTL_API
     JS_FN("normalize",         str_normalize,         0,JSFUN_GENERIC_NATIVE),
 #endif
 
     /* Perl-ish methods (search is actually Python-esque). */
-    JS_FN("match",             str_match,             1,JSFUN_GENERIC_NATIVE),
+    JS_SELF_HOSTED_FN("match", "String_match",        1,0),
     JS_FN("search",            str_search,            1,JSFUN_GENERIC_NATIVE),
     JS_INLINABLE_FN("replace", str_replace,           2,JSFUN_GENERIC_NATIVE, StringReplace),
     JS_INLINABLE_FN("split",   str_split,             2,JSFUN_GENERIC_NATIVE, StringSplit),
     JS_SELF_HOSTED_FN("substr", "String_substr",      2,0),
 
     /* Python-esque sequence methods. */
     JS_FN("concat",            str_concat,            1,JSFUN_GENERIC_NATIVE),
     JS_SELF_HOSTED_FN("slice", "String_slice",        2,0),
@@ -4363,16 +4144,18 @@ static const JSFunctionSpec string_stati
     JS_INLINABLE_FN("fromCharCode", js::str_fromCharCode, 1, 0, StringFromCharCode),
 
     JS_SELF_HOSTED_FN("fromCodePoint",   "String_static_fromCodePoint", 1,0),
     JS_SELF_HOSTED_FN("raw",             "String_static_raw",           2,JSFUN_HAS_REST),
     JS_SELF_HOSTED_FN("substring",       "String_static_substring",     3,0),
     JS_SELF_HOSTED_FN("substr",          "String_static_substr",        3,0),
     JS_SELF_HOSTED_FN("slice",           "String_static_slice",         3,0),
 
+    JS_SELF_HOSTED_FN("match",           "String_generic_match",        2,0),
+
     // This must be at the end because of bug 853075: functions listed after
     // self-hosted methods aren't available in self-hosted code.
 #if EXPOSE_INTL_API
     JS_SELF_HOSTED_FN("localeCompare",   "String_static_localeCompare", 2,0),
 #endif
     JS_FS_END
 };
 
@@ -5394,8 +5177,132 @@ js::PutEscapedStringImpl(char* buffer, s
 
 template size_t
 js::PutEscapedString(char* buffer, size_t bufferSize, const Latin1Char* chars, size_t length,
                      uint32_t quote);
 
 template size_t
 js::PutEscapedString(char* buffer, size_t bufferSize, const char16_t* chars, size_t length,
                      uint32_t quote);
+
+static bool
+FlatStringMatchHelper(JSContext* cx, HandleString str, HandleString pattern, bool* isFlat, int32_t* match)
+{
+    RootedLinearString linearPattern(cx, pattern->ensureLinear(cx));
+    if (!linearPattern)
+        return false;
+
+    static const size_t MAX_FLAT_PAT_LEN = 256;
+    if (linearPattern->length() > MAX_FLAT_PAT_LEN || StringHasRegExpMetaChars(linearPattern)) {
+        *isFlat = false;
+        return true;
+    }
+
+    *isFlat = true;
+    if (str->isRope()) {
+        if (!RopeMatch(cx, &str->asRope(), linearPattern, match))
+            return false;
+    } else {
+        *match = StringMatch(&str->asLinear(), linearPattern);
+    }
+
+    return true;
+}
+
+static bool
+BuildFlatMatchArray(JSContext* cx, HandleString str, HandleString pattern, int32_t match,
+                    MutableHandleValue rval)
+{
+    if (match < 0) {
+        rval.setNull();
+        return true;
+    }
+
+    /* Get the templateObject that defines the shape and type of the output object */
+    JSObject* templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
+    if (!templateObject)
+        return false;
+
+    RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, 1, templateObject));
+    if (!arr)
+        return false;
+
+    /* Store a Value for each pair. */
+    arr->setDenseInitializedLength(1);
+    arr->initDenseElement(0, StringValue(pattern));
+
+    /* Set the |index| property. (TemplateObject positions it in slot 0) */
+    arr->setSlot(0, Int32Value(match));
+
+    /* Set the |input| property. (TemplateObject positions it in slot 1) */
+    arr->setSlot(1, StringValue(str));
+
+#ifdef DEBUG
+    RootedValue test(cx);
+    RootedId id(cx, NameToId(cx->names().index));
+    if (!NativeGetProperty(cx, arr, id, &test))
+        return false;
+    MOZ_ASSERT(test == arr->getSlot(0));
+    id = NameToId(cx->names().input);
+    if (!NativeGetProperty(cx, arr, id, &test))
+        return false;
+    MOZ_ASSERT(test == arr->getSlot(1));
+#endif
+
+    rval.setObject(*arr);
+    return true;
+}
+
+#ifdef DEBUG
+static bool
+CallIsStringOptimizable(JSContext* cx, const char* name, bool* result)
+{
+    JSAtom* atom = Atomize(cx, name, strlen(name));
+    if (!atom)
+        return false;
+    RootedPropertyName propName(cx, atom->asPropertyName());
+
+    RootedValue funcVal(cx);
+    if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), propName, propName, 0, &funcVal))
+        return false;
+
+    InvokeArgs args(cx);
+    if (!args.init(0))
+        return false;
+    args.setCallee(funcVal);
+    args.setThis(UndefinedValue());
+    if (!Invoke(cx, args))
+        return false;
+
+    *result = args.rval().toBoolean();
+    return true;
+}
+#endif
+
+bool
+js::FlatStringMatch(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(args[0].isString());
+    MOZ_ASSERT(args[1].isString());
+#ifdef DEBUG
+    bool isOptimizable = false;
+    if (!CallIsStringOptimizable(cx, "IsStringMatchOptimizable", &isOptimizable))
+        return false;
+    MOZ_ASSERT(isOptimizable);
+#endif
+
+    RootedString str(cx,args[0].toString());
+    RootedString pattern(cx, args[1].toString());
+
+    bool isFlat = false;
+    int32_t match = 0;
+    if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
+        return false;
+
+    if (!isFlat) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    return BuildFlatMatchArray(cx, str, pattern, match, args.rval());
+}
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -426,19 +426,16 @@ FileEscapedString(FILE* fp, const char* 
 {
     Fprinter out(fp);
     bool res = EscapedStringPrinter(out, chars, length, quote);
     out.finish();
     return res;
 }
 
 bool
-str_match(JSContext* cx, unsigned argc, Value* vp);
-
-bool
 str_search(JSContext* cx, unsigned argc, Value* vp);
 
 bool
 str_split(JSContext* cx, unsigned argc, Value* vp);
 
 JSObject*
 str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep);
 
@@ -448,11 +445,14 @@ str_flat_replace_string(JSContext *cx, H
 
 JSString*
 str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
                        HandleString replacement);
 
 extern bool
 StringConstructor(JSContext* cx, unsigned argc, Value* vp);
 
+extern bool
+FlatStringMatch(JSContext* cx, unsigned argc, Value* vp);
+
 } /* namespace js */
 
 #endif /* jsstr_h */
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/RegExpExec-exec.js
@@ -0,0 +1,18 @@
+var BUGNUMBER = 887016;
+var summary = "RegExpExec should throw if exec property of non-RegExp is not callable";
+
+print(BUGNUMBER + ": " + summary);
+
+for (var exec of [null, 0, false, undefined, ""]) {
+  // RegExp with non-callable exec
+  var re = /a/;
+  re.exec = exec;
+  RegExp.prototype[Symbol.match].call(re, "foo");
+
+  // non-RegExp with non-callable exec
+  assertThrowsInstanceOf(() => RegExp.prototype[Symbol.match].call({ exec }, "foo"),
+                         TypeError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/RegExpExec-return.js
@@ -0,0 +1,31 @@
+var BUGNUMBER = 887016;
+var summary = "RegExpExec should throw if returned value is not an object nor null.";
+
+print(BUGNUMBER + ": " + summary);
+
+for (var ret of [null, {}, [], /a/]) {
+  assertEq(RegExp.prototype[Symbol.match].call({
+    get global() {
+      return false;
+    },
+    exec(S) {
+      return ret;
+    }
+  }, "foo"), ret);
+}
+
+for (ret of [undefined, 1, true, false, Symbol.iterator]) {
+  assertThrowsInstanceOf(() => {
+    RegExp.prototype[Symbol.match].call({
+      get global() {
+        return false;
+      },
+      exec(S) {
+        return ret;
+      }
+    }, "foo");
+  }, TypeError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/RegExp/constructor-constructor.js
+++ b/js/src/tests/ecma_6/RegExp/constructor-constructor.js
@@ -20,16 +20,34 @@ assertEq(RegExp(re) === re, false);
 assertEq(RegExp(re).toString(), re.toString());
 
 
 re = new Proxy(/a/, {
   get(that, name) {
     return that[name];
   }
 });
+assertEq(RegExp(re), re);
+re = new Proxy(/a/, {
+  get(that, name) {
+    if (name == "constructor") {
+      return function() {};
+    }
+    return that[name];
+  }
+});
+assertEq(RegExp(re) === re, false);
+re = new Proxy(/a/, {
+  get(that, name) {
+    if (name == Symbol.match) {
+      return undefined;
+    }
+    return that[name];
+  }
+});
 assertEq(RegExp(re) === re, false);
 
 re = new Proxy(g.eval(`/a/`), {
   get(that, name) {
     return that[name];
   }
 });
 assertEq(RegExp(re) === re, false);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/match-this.js
@@ -0,0 +1,12 @@
+var BUGNUMBER = 887016;
+var summary = "RegExp.prototype[@@match] should check this value.";
+
+print(BUGNUMBER + ": " + summary);
+
+for (var v of [null, 1, true, undefined, "", Symbol.iterator]) {
+  assertThrowsInstanceOf(() => RegExp.prototype[Symbol.match].call(v),
+                         TypeError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/match-trace.js
@@ -0,0 +1,147 @@
+var BUGNUMBER = 887016;
+var summary = "Trace RegExp.prototype[@@match] behavior.";
+
+print(BUGNUMBER + ": " + summary);
+
+var n;
+var log;
+var target;
+var global;
+var unicode;
+var logProxy;
+
+var execResult;
+var lastIndexResult;
+var lastIndexExpected;
+
+function P(A) {
+  return new Proxy(A, {
+    get(that, name) {
+      if (logProxy)
+        log += "get:result[" + name + "],";
+      return that[name];
+    }
+  });
+}
+
+var myRegExp = {
+  get global() {
+    log += "get:global,";
+    return global;
+  },
+  get lastIndex() {
+    log += "get:lastIndex,";
+    return lastIndexResult[n];
+  },
+  set lastIndex(v) {
+    log += "set:lastIndex,";
+    assertEq(v, lastIndexExpected[n]);
+  },
+  get unicode() {
+    log += "get:unicode,";
+    return unicode;
+  },
+  get exec() {
+    log += "get:exec,";
+    return function(S) {
+      log += "call:exec,";
+      assertEq(S, target);
+      return execResult[n++];
+    };
+  },
+};
+
+function reset() {
+  n = 0;
+  log = "";
+  target = "abcAbcABC";
+  global = true;
+  unicode = false;
+  logProxy = true;
+}
+
+// Trace global with non-empty match.
+reset();
+execResult        = [    P(["abc"]), P(["ABC"]), null ];
+lastIndexResult   = [ ,  ,           ,                ];
+lastIndexExpected = [ 0, ,           ,                ];
+var ret = RegExp.prototype[Symbol.match].call(myRegExp, target);
+assertEq(JSON.stringify(ret), `["abc","ABC"]`);
+assertEq(log,
+         "get:global," +
+         "get:unicode," +
+         "set:lastIndex," +
+         "get:exec,call:exec,get:result[0]," +
+         "get:exec,call:exec,get:result[0]," +
+         "get:exec,call:exec,");
+
+// Trace global with empty match.
+reset();
+execResult        = [    P([""]), P([""]), null ];
+lastIndexResult   = [ ,  4,       20,           ];
+lastIndexExpected = [ 0, 5,       21,           ];
+ret = RegExp.prototype[Symbol.match].call(myRegExp, target);
+assertEq(JSON.stringify(ret), `["",""]`);
+assertEq(log,
+         "get:global," +
+         "get:unicode," +
+         "set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,");
+
+// Trace global and unicode with empty match.
+// 1. not surrogate pair
+// 2. lead surrogate pair
+// 3. trail surrogate pair
+// 4. lead surrogate pair without trail surrogate pair
+// 5. index overflow
+reset();
+unicode = true;
+//        0123     4     5678
+target = "___\uD83D\uDC38___\uD83D";
+execResult        = [    P([""]), P([""]), P([""]), P([""]), P([""]), null ];
+lastIndexResult   = [ ,  2,       3,       4,       8,       9,            ];
+lastIndexExpected = [ 0, 3,       5,       5,       9,       10,           ];
+ret = RegExp.prototype[Symbol.match].call(myRegExp, target);
+assertEq(JSON.stringify(ret), `["","","","",""]`);
+assertEq(log,
+         "get:global," +
+         "get:unicode," +
+         "set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,get:result[0],get:lastIndex,set:lastIndex," +
+         "get:exec,call:exec,");
+
+// Trace global with no match.
+reset();
+execResult        = [    null ];
+lastIndexResult   = [ ,       ];
+lastIndexExpected = [ 0,      ];
+ret = RegExp.prototype[Symbol.match].call(myRegExp, target);
+assertEq(ret, null);
+assertEq(log,
+         "get:global," +
+         "get:unicode," +
+         "set:lastIndex," +
+         "get:exec,call:exec,");
+
+// Trace non-global.
+reset();
+global = false;
+execResult        = [ P(["abc"]) ];
+lastIndexResult   = [];
+lastIndexExpected = [];
+ret = RegExp.prototype[Symbol.match].call(myRegExp, target);
+// ret is the Proxy on non-global case, disable logging.
+logProxy = false;
+assertEq(JSON.stringify(ret), `["abc"]`);
+assertEq(log,
+         "get:global," +
+         "get:exec,call:exec,");
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/match.js
@@ -0,0 +1,36 @@
+var BUGNUMBER = 887016;
+var summary = "Implement RegExp.prototype[@@match].";
+
+print(BUGNUMBER + ": " + summary);
+
+assertEq(RegExp.prototype[Symbol.match].name, "[Symbol.match]");
+assertEq(RegExp.prototype[Symbol.match].length, 1);
+var desc = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.match);
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.writable, true);
+
+var re = /a/;
+var v = re[Symbol.match]("abcAbcABC");
+assertEq(Array.isArray(v), true);
+assertEq(v.length, 1);
+assertEq(v[0], "a");
+
+re = /d/;
+v = re[Symbol.match]("abcAbcABC");
+assertEq(v, null);
+
+re = /a/ig;
+v = re[Symbol.match]("abcAbcABC");
+assertEq(Array.isArray(v), true);
+assertEq(v.length, 3);
+assertEq(v[0], "a");
+assertEq(v[1], "A");
+assertEq(v[2], "A");
+
+re = /d/g;
+v = re[Symbol.match]("abcAbcABC");
+assertEq(v, null);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/String/match.js
@@ -0,0 +1,31 @@
+var BUGNUMBER = 887016;
+var summary = "Call RegExp.prototype[@@match] from String.prototype.match.";
+
+print(BUGNUMBER + ": " + summary);
+
+var called = 0;
+var myRegExp = {
+  [Symbol.match](S) {
+    assertEq(S, "abcAbcABC");
+    called++;
+    return 42;
+  }
+};
+assertEq("abcAbcABC".match(myRegExp), 42);
+assertEq(called, 1);
+
+var origMatch = RegExp.prototype[Symbol.match];
+
+called = 0;
+RegExp.prototype[Symbol.match] = function(S) {
+  assertEq(S, "abcAbcABC");
+  called++;
+  return 43;
+};
+assertEq("abcAbcABC".match("abc"), 43);
+assertEq(called, 1);
+
+RegExp.prototype[Symbol.match] = origMatch;
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -450,16 +450,24 @@ GlobalObject::initSelfHostingBuiltins(JS
     RootedValue std_iterator(cx);
     std_iterator.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::iterator));
     if (!JS_DefineProperty(cx, global, "std_iterator", std_iterator,
                            JSPROP_PERMANENT | JSPROP_READONLY))
     {
         return false;
     }
 
+    RootedValue std_match(cx);
+    std_match.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::match));
+    if (!JS_DefineProperty(cx, global, "std_match", std_match,
+                           JSPROP_PERMANENT | JSPROP_READONLY))
+    {
+        return false;
+    }
+
     RootedValue std_species(cx);
     std_species.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::species));
     if (!JS_DefineProperty(cx, global, "std_species", std_species,
                            JSPROP_PERMANENT | JSPROP_READONLY))
     {
         return false;
     }
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -13,16 +13,17 @@
 
 #include "jsarray.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsdate.h"
 #include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jshashutil.h"
+#include "jsstr.h"
 #include "jsweakmap.h"
 #include "jswrapper.h"
 #include "selfhosted.out.h"
 
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/Object.h"
@@ -2069,16 +2070,42 @@ intrinsic_captureCurrentStack(JSContext*
     RootedObject stack(cx);
     if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount))
         return false;
 
     args.rval().setObject(*stack);
     return true;
 }
 
+static bool
+IsMatchFlagsArgumentEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    args.rval().setBoolean(cx->runtime()->options().matchFlagArgument());
+    return true;
+}
+
+static bool
+WarnOnceAboutFlagsArgument(JSContext* cx, unsigned argc, Value* vp)
+{
+    if (!cx->compartment()->warnedAboutFlagsArgument) {
+        if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
+                                          cx->runtime()->options().matchFlagArgument()
+                                          ? JSMSG_DEPRECATED_FLAGS_ARG
+                                          : JSMSG_OBSOLETE_FLAGS_ARG))
+        {
+            return false;
+        }
+        cx->compartment()->warnedAboutFlagsArgument = true;
+    }
+    return true;
+}
+
 // The self-hosting global isn't initialized with the normal set of builtins.
 // Instead, individual C++-implemented functions that're required by
 // self-hosted code are defined as global functions. Accessing these
 // functions via a content compartment's builtins would be unsafe, because
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
@@ -2126,17 +2153,16 @@ static const JSFunctionSpec intrinsic_fu
 
     JS_FN("std_Set_has",                         SetObject::has,               1,0),
     JS_FN("std_Set_iterator",                    SetObject::values,            0,0),
 
     JS_INLINABLE_FN("std_String_fromCharCode",   str_fromCharCode,             1,0, StringFromCharCode),
     JS_INLINABLE_FN("std_String_charCodeAt",     str_charCodeAt,               1,0, StringCharCodeAt),
     JS_FN("std_String_indexOf",                  str_indexOf,                  1,0),
     JS_FN("std_String_lastIndexOf",              str_lastIndexOf,              1,0),
-    JS_FN("std_String_match",                    str_match,                    1,0),
     JS_INLINABLE_FN("std_String_replace",        str_replace,                  2,0, StringReplace),
     JS_INLINABLE_FN("std_String_split",          str_split,                    2,0, StringSplit),
     JS_FN("std_String_startsWith",               str_startsWith,               1,0),
     JS_FN("std_String_toLowerCase",              str_toLowerCase,              0,0),
     JS_FN("std_String_toUpperCase",              str_toUpperCase,              0,0),
 
     JS_FN("std_WeakMap_has",                     WeakMap_has,                  1,0),
     JS_FN("std_WeakMap_get",                     WeakMap_get,                  2,0),
@@ -2389,21 +2415,26 @@ static const JSFunctionSpec intrinsic_fu
     JS_INLINABLE_FN("RegExpTester", RegExpTester, 4,0,
                     RegExpTester),
     JS_FN("RegExpCreate", intrinsic_RegExpCreate, 2,0),
     JS_INLINABLE_FN("RegExpPrototypeOptimizable", RegExpPrototypeOptimizable, 1,0,
                     RegExpPrototypeOptimizable),
     JS_INLINABLE_FN("RegExpInstanceOptimizable", RegExpInstanceOptimizable, 1,0,
                     RegExpInstanceOptimizable),
 
+    JS_FN("FlatStringMatch", FlatStringMatch, 2,0),
+
     // See builtin/RegExp.h for descriptions of the regexp_* functions.
     JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
     JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
     JS_FN("regexp_construct", regexp_construct_self_hosting, 2,0),
 
+    JS_FN("IsMatchFlagsArgumentEnabled", IsMatchFlagsArgumentEnabled, 0,0),
+    JS_FN("WarnOnceAboutFlagsArgument", WarnOnceAboutFlagsArgument, 0,0),
+
     JS_FN("IsModule", intrinsic_IsInstanceOfBuiltin<ModuleObject>, 1, 0),
     JS_FN("CallModuleMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<ModuleObject>>, 2, 0),
     JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0),
     JS_FN("GetModuleEnvironment", intrinsic_GetModuleEnvironment, 1, 0),
     JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 1, 0),
     JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0),
     JS_FN("CreateNamespaceBinding", intrinsic_CreateNamespaceBinding, 3, 0),
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -214,16 +214,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   gPrototypeProperties['Function'] =
     ["constructor", "toSource", "toString", "apply", "call", "bind",
      "isGenerator", "length", "name", "arguments", "caller"];
   gConstructorProperties['Function'] = constructorProps([])
 
   gPrototypeProperties['RegExp'] =
     ["constructor", "toSource", "toString", "compile", "exec", "test",
+     Symbol.match,
      "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode",
      "lastIndex"];
   gConstructorProperties['RegExp'] =
     constructorProps(["input", "lastMatch", "lastParen",
                       "leftContext", "rightContext", "$1", "$2", "$3", "$4",
                       "$5", "$6", "$7", "$8", "$9", "$_", "$&", "$+",
                       "$`", "$'"])