Bug 887016 - Part 9: Implement RegExp.prototype[@@match] and call it from String.prototype.match. r=till
☠☠ backed out by 248bb4773adf ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Sat, 05 Sep 2015 21:55:06 +0900
changeset 290627 537d40121b6d4c80071e02aeaa95712fdfdcb107
parent 290626 4be734a1452428151831dbbc6115ac10b20083f7
child 290628 f23a61067cefec53fd72dc00383092f72fe707cb
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs887016
milestone48.0a1
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);
+}
+
 /* ES6 Draft Oct 14, 2014 21.1.3.19 */
 function String_substring(start, end) {
     // Steps 1-3.
     RequireObjectCoercible(this);
     var str = ToString(this);
 
     // Step 4.
     var len = str.length;
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;
 
@@ -4206,17 +3987,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),
@@ -4361,16 +4142,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
 };
 
@@ -5392,8 +5175,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
@@ -448,16 +448,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
@@ -222,16 +222,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", "$_", "$&", "$+",
                       "$`", "$'"])