diff --git a/js/public/RegExpFlags.h b/js/public/RegExpFlags.h index d75b5f6547eb..4fa36031b030 100644 --- a/js/public/RegExpFlags.h +++ b/js/public/RegExpFlags.h @@ -19,7 +19,7 @@ namespace JS { /** * Regular expression flag values, suitable for initializing a collection of * regular expression flags as defined below in |RegExpFlags|. Flags are listed - * in alphabetical order by syntax -- /g, /i, /m, /s, /u, /y. + * in alphabetical order by syntax -- /d, /g, /i, /m, /s, /u, /y. */ class RegExpFlag { // WARNING TO SPIDERMONKEY HACKERS (embedders must assume these values can @@ -30,35 +30,40 @@ class RegExpFlag { // ascending order) unless you also add a translation layer. public: + /** + * Add .indices property to the match result, i.e. /d + */ + static constexpr uint8_t HasIndices = 0b100'0000; + /** * Act globally and find *all* matches (rather than stopping after just the * first one), i.e. /g. */ - static constexpr uint8_t Global = 0b00'0010; + static constexpr uint8_t Global = 0b000'0010; /** * Interpret regular expression source text case-insensitively by folding * uppercase letters to lowercase, i.e. /i. */ - static constexpr uint8_t IgnoreCase = 0b00'0001; + static constexpr uint8_t IgnoreCase = 0b000'0001; /** Treat ^ and $ as begin and end of line, i.e. /m. */ - static constexpr uint8_t Multiline = 0b00'0100; + static constexpr uint8_t Multiline = 0b000'0100; /* Allow . to match newline characters, i.e. /s. */ - static constexpr uint8_t DotAll = 0b10'0000; + static constexpr uint8_t DotAll = 0b010'0000; /** Use Unicode semantics, i.e. /u. */ - static constexpr uint8_t Unicode = 0b01'0000; + static constexpr uint8_t Unicode = 0b001'0000; /** Only match starting from .lastIndex, i.e. /y. */ - static constexpr uint8_t Sticky = 0b00'1000; + static constexpr uint8_t Sticky = 0b000'1000; /** No regular expression flags. */ - static constexpr uint8_t NoFlags = 0b00'0000; + static constexpr uint8_t NoFlags = 0b000'0000; /** All regular expression flags. */ - static constexpr uint8_t AllFlags = 0b11'1111; + static constexpr uint8_t AllFlags = 0b111'1111; }; /** @@ -108,6 +113,7 @@ class RegExpFlags { return RegExpFlags(~flags_ & RegExpFlag::AllFlags); } + bool hasIndices() const { return flags_ & RegExpFlag::HasIndices; } bool global() const { return flags_ & RegExpFlag::Global; } bool ignoreCase() const { return flags_ & RegExpFlag::IgnoreCase; } bool multiline() const { return flags_ & RegExpFlag::Multiline; } diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 23eff01eccf2..c8f23cdc53bc 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -706,6 +706,14 @@ static bool RegExpGetter(JSContext* cx, CallArgs& args, const char* methodName, return false; } +bool js::regexp_hasIndices(JSContext* cx, unsigned argc, JS::Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return RegExpGetter(cx, args, "hasIndices", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->hasIndices()); + return true; + }); +} + // ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 // 21.2.5.5 get RegExp.prototype.global bool js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp) { @@ -794,6 +802,7 @@ bool js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) { const JSPropertySpec js::regexp_properties[] = { JS_SELF_HOSTED_GET("flags", "$RegExpFlagsGetter", 0), + JS_PSG("hasIndices", regexp_hasIndices, 0), JS_PSG("global", regexp_global, 0), JS_PSG("ignoreCase", regexp_ignoreCase, 0), JS_PSG("multiline", regexp_multiline, 0), @@ -1820,6 +1829,16 @@ bool js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto) { return false; } + JSNative hasIndicesGetter; + if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().hasIndices), + &hasIndicesGetter)) { + return false; + } + + if (hasIndicesGetter != regexp_hasIndices) { + return false; + } + JSNative ignoreCaseGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().ignoreCase), &ignoreCaseGetter)) { diff --git a/js/src/builtin/RegExp.h b/js/src/builtin/RegExp.h index 1f9b4a277327..66311417b7f1 100644 --- a/js/src/builtin/RegExp.h +++ b/js/src/builtin/RegExp.h @@ -126,6 +126,8 @@ extern const JSPropertySpec regexp_properties[]; extern const JSFunctionSpec regexp_methods[]; // Used in RegExpObject::isOriginalFlagGetter. +[[nodiscard]] extern bool regexp_hasIndices(JSContext* cx, unsigned argc, + JS::Value* vp); [[nodiscard]] extern bool regexp_global(JSContext* cx, unsigned argc, JS::Value* vp); [[nodiscard]] extern bool regexp_ignoreCase(JSContext* cx, unsigned argc, diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js index 909cd69c8b58..f90fa31480e9 100644 --- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -16,30 +16,34 @@ function $RegExpFlagsGetter() { var result = ""; // Steps 4-5. + if (R.hasIndices) + result += "d"; + + // Steps 6-7. if (R.global) result += "g"; - // Steps 6-7. + // Steps 8-9. if (R.ignoreCase) result += "i"; - // Steps 8-9. + // Steps 10-11. if (R.multiline) result += "m"; - // Steps 10-11. + // Steps 12-13. if (R.dotAll) result += "s"; - // Steps 12-13. + // Steps 14-15. if (R.unicode) result += "u"; - // Steps 14-15. + // Steps 16-17 if (R.sticky) result += "y"; - // Step 16. + // Step 18. return result; } _SetCanonicalName($RegExpFlagsGetter, "get flags"); @@ -229,6 +233,7 @@ function RegExpGlobalMatchOpt(rx, S, fullUnicode) { // Checks if following properties and getters are not modified, and accessing // them not observed by content script: // * flags +// * hasIndices // * global // * ignoreCase // * multiline diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h index dd23cfbd8e91..18024dd2fc90 100644 --- a/js/src/builtin/SelfHostingDefines.h +++ b/js/src/builtin/SelfHostingDefines.h @@ -91,6 +91,7 @@ #define REGEXP_STICKY_FLAG 0x08 #define REGEXP_UNICODE_FLAG 0x10 #define REGEXP_DOTALL_FLAG 0x20 +#define REGEXP_HASINDICES_FLAG 0x40 #define REGEXP_STRING_ITERATOR_REGEXP_SLOT 0 #define REGEXP_STRING_ITERATOR_STRING_SLOT 1 diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 7fe72160552b..9e0dbcb37f84 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -2649,7 +2649,9 @@ template while (true) { uint8_t flag; unit = getCodeUnit(); - if (unit == 'g') { + if (unit == 'd') { + flag = RegExpFlag::HasIndices; + } else if (unit == 'g') { flag = RegExpFlag::Global; } else if (unit == 'i') { flag = RegExpFlag::IgnoreCase; diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h index 53e8d2d4fb00..e8ba8b2b3e98 100644 --- a/js/src/jsapi-tests/tests.h +++ b/js/src/jsapi-tests/tests.h @@ -186,6 +186,9 @@ class JSAPITest { JSAPITestString toSource(JS::RegExpFlags flags) { JSAPITestString str; + if (flags.hasIndices()) { + str += "d"; + } if (flags.global()) { str += "g"; } diff --git a/js/src/tests/non262/RegExp/flags.js b/js/src/tests/non262/RegExp/flags.js index 0e7545aeee83..17435f7922c0 100644 --- a/js/src/tests/non262/RegExp/flags.js +++ b/js/src/tests/non262/RegExp/flags.js @@ -12,7 +12,7 @@ assertEq(genericFlags({}), ""); assertEq(genericFlags({ignoreCase: true}), "i"); assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "uy"); assertEq(genericFlags({__proto__: {multiline: true}}), "m"); -assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimsuy"); +assertEq(genericFlags(new Proxy({}, {get(){return true}})), "dgimsuy"); assertThrowsInstanceOf(() => genericFlags(), TypeError); assertThrowsInstanceOf(() => genericFlags(1), TypeError); diff --git a/js/src/tests/non262/RegExp/prototype.js b/js/src/tests/non262/RegExp/prototype.js index bad6476f6025..d23336fcd555 100644 --- a/js/src/tests/non262/RegExp/prototype.js +++ b/js/src/tests/non262/RegExp/prototype.js @@ -1,7 +1,7 @@ const t = RegExp.prototype; let properties = "toString,compile,exec,test," + - "flags,dotAll,global,ignoreCase,multiline,source,sticky,unicode," + + "flags,dotAll,global,hasIndices,ignoreCase,multiline,source,sticky,unicode," + "constructor," + "Symbol(Symbol.match),Symbol(Symbol.replace),Symbol(Symbol.search),Symbol(Symbol.split)"; if (Object.prototype.toSource) { diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index b800f9c551fc..07a84c7eb8db 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -214,6 +214,7 @@ MACRO_(Handle, Handle, "Handle") \ MACRO_(has, has, "has") \ MACRO_(hashConstructor, hashConstructor, "#constructor") \ + MACRO_(hasIndices, hasIndices, "hasIndices") \ MACRO_(hasOwn, hasOwn, "hasOwn") \ MACRO_(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \ MACRO_(highWaterMark, highWaterMark, "highWaterMark") \ diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 47ee0d3e72f4..cc317513bcbb 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -49,6 +49,8 @@ using mozilla::PodCopy; using JS::AutoCheckCannotGC; +static_assert(RegExpFlag::HasIndices == REGEXP_HASINDICES_FLAG, + "self-hosted JS and /d flag bits must agree"); static_assert(RegExpFlag::Global == REGEXP_GLOBAL_FLAG, "self-hosted JS and /g flag bits must agree"); static_assert(RegExpFlag::IgnoreCase == REGEXP_IGNORECASE_FLAG, @@ -120,6 +122,10 @@ RegExpShared* RegExpObject::getShared(JSContext* cx, /* static */ bool RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlags* mask) { + if (native == regexp_hasIndices) { + *mask = RegExpFlag::HasIndices; + return true; + } if (native == regexp_global) { *mask = RegExpFlag::Global; return true; @@ -465,6 +471,9 @@ JSLinearString* RegExpObject::toString(JSContext* cx, sb.infallibleAppend('/'); // Steps 5-7. + if (obj->hasIndices() && !sb.append('d')) { + return nullptr; + } if (obj->global() && !sb.append('g')) { return nullptr; } @@ -984,6 +993,9 @@ static bool ParseRegExpFlags(const CharT* chars, size_t length, for (size_t i = 0; i < length; i++) { uint8_t flag; switch (chars[i]) { + case 'd': + flag = RegExpFlag::HasIndices; + break; case 'g': flag = RegExpFlag::Global; break; diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 70d476fe7692..d9d0a065c467 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -132,6 +132,7 @@ class RegExpObject : public NativeObject { setFixedSlot(FLAGS_SLOT, Int32Value(flags.value())); } + bool hasIndices() const { return getFlags().hasIndices(); } bool global() const { return getFlags().global(); } bool ignoreCase() const { return getFlags().ignoreCase(); } bool multiline() const { return getFlags().multiline(); } diff --git a/js/src/vm/RegExpShared.h b/js/src/vm/RegExpShared.h index fabbfdbc598c..5b5853e36513 100644 --- a/js/src/vm/RegExpShared.h +++ b/js/src/vm/RegExpShared.h @@ -211,6 +211,7 @@ class RegExpShared JS::RegExpFlags getFlags() const { return flags; } + bool hasIndices() const { return flags.hasIndices(); } bool global() const { return flags.global(); } bool ignoreCase() const { return flags.ignoreCase(); } bool multiline() const { return flags.multiline(); } diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xhtml b/js/xpconnect/tests/chrome/test_xrayToJS.xhtml index 52bcb0774aa4..fd7efd2b005d 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xhtml +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xhtml @@ -270,7 +270,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681 gPrototypeProperties['RegExp'] = ["constructor", "toSource", "toString", "compile", "exec", "test", Symbol.match, Symbol.matchAll, Symbol.replace, Symbol.search, Symbol.split, - "flags", "dotAll", "global", "ignoreCase", "multiline", "source", "sticky", "unicode"]; + "flags", "dotAll", "global", "hasIndices", "ignoreCase", "multiline", "source", "sticky", + "unicode"]; gConstructorProperties['RegExp'] = constructorProps(["input", "lastMatch", "lastParen", "leftContext", "rightContext", "$1", "$2", "$3", "$4",