diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index f3d34762f6..198610438c 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -818,15 +818,42 @@ js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) return CallNonGenericMethod(cx, args); } +// ES 2022 22.2.5.6. +MOZ_ALWAYS_INLINE bool +regexp_hasIndices_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); + + // Step 2.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 3-5. + Rooted reObj(cx, &args.thisv().toObject().as()); + args.rval().setBoolean(reObj->hasIndices()); + return true; +} + +bool +js::regexp_hasIndices(JSContext* cx, unsigned argc, JS::Value* vp) +{ + // Steps 1-3. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + 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), + JS_PSG("dotAll", regexp_dotAll, 0), JS_PSG("source", regexp_source, 0), JS_PSG("sticky", regexp_sticky, 0), JS_PSG("unicode", regexp_unicode, 0), - JS_PSG("dotAll", regexp_dotAll, 0), JS_PS_END }; @@ -1802,6 +1829,13 @@ js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto) if (!IsSelfHostedFunctionWithName(flagsGetter, cx->names().RegExpFlagsGetter)) return false; + JSNative hasIndicesGetter; + if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().hasIndices), &hasIndicesGetter)) + return false; + + if (hasIndicesGetter != regexp_hasIndices) + return false; + JSNative globalGetter; if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().global), &globalGetter)) return false; diff --git a/js/src/builtin/RegExp.h b/js/src/builtin/RegExp.h index c0a7d59f77..b15c3d82f4 100644 --- a/js/src/builtin/RegExp.h +++ b/js/src/builtin/RegExp.h @@ -124,17 +124,19 @@ extern const JSFunctionSpec regexp_methods[]; // Used in RegExpObject::isOriginalFlagGetter. extern MOZ_MUST_USE bool +regexp_hasIndices(JSContext* cx, unsigned argc, JS::Value* vp); +extern MOZ_MUST_USE bool regexp_global(JSContext* cx, unsigned argc, JS::Value* vp); extern MOZ_MUST_USE bool regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp); extern MOZ_MUST_USE bool regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp); extern MOZ_MUST_USE bool +regexp_dotAll(JSContext* cx, unsigned argc, JS::Value* vp); +extern MOZ_MUST_USE bool regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp); extern MOZ_MUST_USE bool regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp); -extern MOZ_MUST_USE bool -regexp_dotAll(JSContext* cx, unsigned argc, JS::Value* vp); } /* namespace js */ diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js index ab4d76f4ca..536b0dac4b 100644 --- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -2,8 +2,7 @@ * 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/. */ -// ES6 draft rev34 (2015/02/20) 21.2.5.3 get RegExp.prototype.flags -// Updated for ES2018 /s (dotAll) +// ES2022 22.2.5.4 get RegExp.prototype.flags function RegExpFlagsGetter() { // Steps 1-2. var R = this; @@ -13,31 +12,36 @@ function RegExpFlagsGetter() { // Step 3. var result = ""; - // Steps 4-6. + // Steps 4-5. + if (R.hasIndices) + result += "d"; + + // Steps 6-7. if (R.global) result += "g"; - // Steps 7-9. + // Steps 8-9. if (R.ignoreCase) result += "i"; - // Steps 10-12. + // Steps 10-11. if (R.multiline) result += "m"; - // Steps 13-15. - if (R.unicode) - result += "u"; - - // Steps 16-18. - if (R.sticky) - result += "y"; - - // ES2018 + // Steps 12-13. if (R.dotAll) result += "s"; - // Step 19. + // Steps 14-15. + if (R.unicode) + result += "u"; + + // Steps 16-17. + if (R.sticky) + result += "y"; + + + // Step 18. return result; } _SetCanonicalName(RegExpFlagsGetter, "get flags"); @@ -227,9 +231,11 @@ 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 +// * dotAll // * sticky // * unicode // * exec diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h index 9a93a2c795..2745fa754b 100644 --- a/js/src/builtin/SelfHostingDefines.h +++ b/js/src/builtin/SelfHostingDefines.h @@ -85,6 +85,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 0130b20abc..5402727cc7 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -2083,7 +2083,9 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) while (true) { if (!peekChar(&c)) goto error; - if (c == 'g' && !(reflags & GlobalFlag)) + if (c == 'd' && !(reflags & HasIndicesFlag)) + reflags = RegExpFlag(reflags | HasIndicesFlag); + else if (c == 'g' && !(reflags & GlobalFlag)) reflags = RegExpFlag(reflags | GlobalFlag); else if (c == 'i' && !(reflags & IgnoreCaseFlag)) reflags = RegExpFlag(reflags | IgnoreCaseFlag); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index f1e71f9e7a..68e852e23f 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5824,12 +5824,13 @@ JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj, bool* isDate); /* * Regular Expressions. */ -#define JSREG_FOLD 0x01u /* fold uppercase to lowercase */ -#define JSREG_GLOB 0x02u /* global exec, creates array of matches */ -#define JSREG_MULTILINE 0x04u /* treat ^ and $ as begin and end of line */ -#define JSREG_STICKY 0x08u /* only match starting at lastIndex */ -#define JSREG_UNICODE 0x10u /* unicode */ -#define JSREG_DOTALL 0x20u /* match . to everything including newlines */ +#define JSREG_FOLD 0x01u /* fold uppercase to lowercase */ +#define JSREG_GLOB 0x02u /* global exec, creates array of matches */ +#define JSREG_MULTILINE 0x04u /* treat ^ and $ as begin and end of line */ +#define JSREG_STICKY 0x08u /* only match starting at lastIndex */ +#define JSREG_UNICODE 0x10u /* unicode */ +#define JSREG_DOTALL 0x20u /* match . to everything including newlines */ +#define JSREG_HASINDICES 0x40u /* add .indices property to the match result */ extern JS_PUBLIC_API(JSObject*) JS_NewRegExpObject(JSContext* cx, const char* bytes, size_t length, unsigned flags); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index ebfad89fa0..809990c0fc 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -183,6 +183,7 @@ macro(groups, groups, "groups") \ macro(Handle, Handle, "Handle") \ macro(has, has, "has") \ + 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 1d88754084..129ed2492f 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -51,6 +51,7 @@ JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE); JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY); JS_STATIC_ASSERT(UnicodeFlag == JSREG_UNICODE); JS_STATIC_ASSERT(DotAllFlag == JSREG_DOTALL); +JS_STATIC_ASSERT(HasIndicesFlag == JSREG_HASINDICES); RegExpObject* js::RegExpAlloc(ExclusiveContext* cx, HandleObject proto /* = nullptr */) @@ -136,6 +137,10 @@ RegExpObject::getShared(JSContext* cx, Handle regexp, /* static */ bool RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask) { + if (native == regexp_hasIndices) { + *mask = HasIndicesFlag; + return true; + } if (native == regexp_global) { *mask = GlobalFlag; return true; @@ -148,6 +153,10 @@ RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask) *mask = MultilineFlag; return true; } + if (native == regexp_dotAll) { + *mask = DotAllFlag; + return true; + } if (native == regexp_sticky) { *mask = StickyFlag; return true; @@ -156,10 +165,6 @@ RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask) *mask = UnicodeFlag; return true; } - if (native == regexp_dotAll) { - *mask = DotAllFlag; - return true; - } return false; } @@ -488,12 +493,16 @@ RegExpObject::toString(JSContext* cx) const sb.infallibleAppend('/'); // Steps 5-7. + if (hasIndices() && !sb.append('d')) + return nullptr; if (global() && !sb.append('g')) return nullptr; if (ignoreCase() && !sb.append('i')) return nullptr; if (multiline() && !sb.append('m')) return nullptr; + if (dotAll() && !sb.append('s')) + return nullptr; if (unicode() && !sb.append('u')) return nullptr; if (sticky() && !sb.append('y')) @@ -1483,14 +1492,18 @@ ParseRegExpFlags(const CharT* chars, size_t length, RegExpFlag* flagsOut, char16 for (size_t i = 0; i < length; i++) { *lastParsedOut = chars[i]; switch (chars[i]) { - case 'i': - if (!HandleRegExpFlag(IgnoreCaseFlag, flagsOut)) + case 'd': + if (!HandleRegExpFlag(HasIndicesFlag, flagsOut)) return false; break; case 'g': if (!HandleRegExpFlag(GlobalFlag, flagsOut)) return false; break; + case 'i': + if (!HandleRegExpFlag(IgnoreCaseFlag, flagsOut)) + return false; + break; case 'm': if (!HandleRegExpFlag(MultilineFlag, flagsOut)) return false; diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 5247731112..b088315dea 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -56,9 +56,10 @@ enum RegExpFlag : uint8_t StickyFlag = 0x08, UnicodeFlag = 0x10, DotAllFlag = 0x20, + HasIndicesFlag = 0x40, NoFlags = 0x00, - AllFlags = 0x3f + AllFlags = 0x7f }; static_assert(IgnoreCaseFlag == REGEXP_IGNORECASE_FLAG && @@ -66,7 +67,8 @@ static_assert(IgnoreCaseFlag == REGEXP_IGNORECASE_FLAG && MultilineFlag == REGEXP_MULTILINE_FLAG && StickyFlag == REGEXP_STICKY_FLAG && UnicodeFlag == REGEXP_UNICODE_FLAG && - DotAllFlag == REGEXP_DOTALL_FLAG, + DotAllFlag == REGEXP_DOTALL_FLAG && + HasIndicesFlag == REGEXP_HASINDICES_FLAG, "Flag values should be in sync with self-hosted JS"); enum RegExpRunStatus @@ -206,12 +208,13 @@ class RegExpShared : public gc::TenuredCell JSAtom* getSource() const { return source; } RegExpFlag getFlags() const { return flags; } - bool ignoreCase() const { return flags & IgnoreCaseFlag; } + bool hasIndices() const { return flags & HasIndicesFlag; } bool global() const { return flags & GlobalFlag; } + bool ignoreCase() const { return flags & IgnoreCaseFlag; } bool multiline() const { return flags & MultilineFlag; } - bool sticky() const { return flags & StickyFlag; } - bool unicode() const { return flags & UnicodeFlag; } bool dotAll() const { return flags & DotAllFlag; } + bool unicode() const { return flags & UnicodeFlag; } + bool sticky() const { return flags & StickyFlag; } bool isCompiled(CompilationMode mode, bool latin1, ForceByteCodeEnum force = DontForceByteCode) const { @@ -451,12 +454,13 @@ class RegExpObject : public NativeObject setSlot(FLAGS_SLOT, Int32Value(flags)); } - bool ignoreCase() const { return getFlags() & IgnoreCaseFlag; } + bool hasIndices() const { return getFlags() & HasIndicesFlag; } bool global() const { return getFlags() & GlobalFlag; } + bool ignoreCase() const { return getFlags() & IgnoreCaseFlag; } bool multiline() const { return getFlags() & MultilineFlag; } - bool sticky() const { return getFlags() & StickyFlag; } - bool unicode() const { return getFlags() & UnicodeFlag; } bool dotAll() const { return getFlags() & DotAllFlag; } + bool unicode() const { return getFlags() & UnicodeFlag; } + bool sticky() const { return getFlags() & StickyFlag; } static bool isOriginalFlagGetter(JSNative native, RegExpFlag* mask);