tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9b7cbea13fbf47b5723739a3329f9491b141a744
parent 47a09d650dd57e45447244d68ef3bff60bc9c70b
Author: Serah Nderi <snderi@igalia.com>
Date:   Wed, 26 Nov 2025 17:39:30 +0000

Bug 1306461 - Normalise and partially disable RegExp legacy features, r=iain

Differential Revision: https://phabricator.services.mozilla.com/D264695

Diffstat:
Mjs/public/friend/ErrorNumbers.msg | 4++++
Mjs/src/builtin/RegExp.cpp | 194++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mjs/src/builtin/RegExp.h | 3++-
Mjs/src/jit-test/tests/basic/testCrossCompartmentTransparency.js | 1-
Ajs/src/jit-test/tests/regexp/legacy-features-enabled.js | 43+++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/CodeGenerator.cpp | 13+++++++++++++
Mjs/src/shell/js.cpp | 7++++++-
Mjs/src/tests/non262/regress/regress-591846.js | 11+++++++----
Mjs/src/tests/test262-update.py | 3+--
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/prop-desc.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-cross-realm-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-not-regexp-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-subclass-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/prop-desc.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-cross-realm-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-not-regexp-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-subclass-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/prop-desc.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-cross-realm-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-not-regexp-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-subclass-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/prop-desc.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-cross-realm-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-not-regexp-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-subclass-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/prop-desc.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-cross-realm-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-not-regexp-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-subclass-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/prop-desc.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-cross-realm-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-not-regexp-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-subclass-constructor.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/prototype/compile/this-cross-realm-instance.js | 2+-
Mjs/src/tests/test262/annexB/built-ins/RegExp/prototype/compile/this-subclass-instance.js | 2+-
Mjs/src/vm/RegExpObject.cpp | 55++++++++++++++++++++++++++++++++++++++++++++-----------
Mjs/src/vm/RegExpObject.h | 47+++++++++++++++++++++++++++++++++++++++++------
Mjs/src/vm/RegExpStatics.h | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Mjs/src/vm/SelfHosting.cpp | 2+-
Mmodules/libpref/init/StaticPrefList.yaml | 7+++++++
40 files changed, 419 insertions(+), 75 deletions(-)

diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg @@ -687,6 +687,10 @@ MSG_DEF(JSMSG_INVALID_CLASS_SET_OP, 0, JSEXN_SYNTAXERR, "invalid class se MSG_DEF(JSMSG_INVALID_CHAR_IN_CLASS, 0, JSEXN_SYNTAXERR, "invalid character in class in regular expression") MSG_DEF(JSMSG_NEGATED_CLASS_WITH_STR, 0, JSEXN_SYNTAXERR, "negated character class with strings in regular expression") MSG_DEF(JSMSG_MULTIPLE_FLAG_DASHES, 0, JSEXN_SYNTAXERR, "multiple dashes in flag group") +MSG_DEF(JSMSG_INCOMPATIBLE_RECEIVER, 1, JSEXN_TYPEERR, "RegExp static property '{0}' called with incompatible receiver") +MSG_DEF(JSMSG_REGEXP_STATIC_EMPTY, 1, JSEXN_TYPEERR, "RegExp static property '{0}' is invalid") +MSG_DEF(JSMSG_REGEXP_CROSS_REALM, 0, JSEXN_TYPEERR, "RegExp operation not permitted on object from different realm") +MSG_DEF(JSMSG_REGEXP_LEGACY_FEATURES_DISABLED, 0, JSEXN_TYPEERR, "RegExp legacy features are disabled") // Typed object MSG_DEF(JSMSG_TYPEDOBJECT_SETTING_IMMUTABLE, 0, JSEXN_ERR, "setting immutable field") diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp @@ -20,6 +20,7 @@ #include "js/PropertySpec.h" #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags #include "util/StringBuilder.h" +#include "vm/EqualityOperations.h" #include "vm/Interpreter.h" #include "vm/JSContext.h" #include "vm/RegExpObject.h" @@ -317,6 +318,27 @@ static int32_t CreateRegExpSearchResult(JSContext* cx, cx->regExpSearcherLastLimit = matches[0].limit; return matches[0].start; } +/* + * https://github.com/tc39/proposal-regexp-legacy-features/blob/master/README.md#regexpbuiltinexec--r-s- + * + */ + +static bool ShouldUpdateRegExpStatics(JSContext* cx, + Handle<RegExpObject*> regexp) { + if (!JS::Prefs::experimental_legacy_regexp()) { + return true; + } + // Step 5. Let thisRealm be the current Realm Record. + JS::Realm* thisRealm = cx->realm(); + // Step 6. Let rRealm be the value of R's [[Realm]] internal slot. + JS::Realm* rRealm = regexp->realm(); + + // Step 7. If SameValue(thisRealm, rRealm) is true, then + if (thisRealm == rRealm) { + return regexp->legacyFeaturesEnabled(); + } + return false; +} /* * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2 @@ -326,14 +348,19 @@ static RegExpRunStatus ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res, MutableHandleRegExpShared re, Handle<JSLinearString*> input, size_t searchIndex, - VectorMatchPairs* matches) { + VectorMatchPairs* matches, + Handle<RegExpObject*> regexp) { RegExpRunStatus status = RegExpShared::execute(cx, re, input, searchIndex, matches); /* Out of spec: Update RegExpStatics. */ if (status == RegExpRunStatus::Success && res) { - if (!res->updateFromMatchPairs(cx, input, *matches)) { - return RegExpRunStatus::Error; + if (ShouldUpdateRegExpStatics(cx, regexp)) { + if (!res->updateFromMatchPairs(cx, input, *matches)) { + return RegExpRunStatus::Error; + } + } else { + res->invalidate(); } } return status; @@ -354,7 +381,7 @@ bool js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, VectorMatchPairs matches; RegExpRunStatus status = - ExecuteRegExpImpl(cx, res, &shared, input, *lastIndex, &matches); + ExecuteRegExpImpl(cx, res, &shared, input, *lastIndex, &matches, reobj); if (status == RegExpRunStatus::Error) { return false; } @@ -474,9 +501,10 @@ static bool RegExpInitializeIgnoringLastIndex(JSContext* cx, /* ES 2016 draft Mar 25, 2016 21.2.3.2.3. */ bool js::RegExpCreate(JSContext* cx, HandleValue patternValue, - HandleValue flagsValue, MutableHandleValue rval) { + HandleValue flagsValue, MutableHandleValue rval, + HandleObject newTarget) { /* Step 1. */ - Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject)); + Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, newTarget)); if (!regexp) { return false; } @@ -546,21 +574,51 @@ static bool SetLastIndex(JSContext* cx, Handle<RegExpObject*> regexp, return SetProperty(cx, regexp, cx->names().lastIndex, val); } -/* ES6 B.2.5.1. */ +/* + * RegExp.prototype.compile ( pattern, flags ) + * https://github.com/tc39/proposal-regexp-legacy-features?tab=readme-ov-file#regexpprototypecompile--pattern-flags- + * ES6 B.2.5.1. + */ MOZ_ALWAYS_INLINE bool regexp_compile_impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsRegExpObject(args.thisv())); Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>()); - // Step 3. + if (JS::Prefs::experimental_legacy_regexp()) { + // Step 3. Let thisRealm be the current Realm Record. + JS::Realm* thisRealm = cx->realm(); + + // Step 4. Let oRealm be the value of O’s [[Realm]] internal slot. + JS::Realm* oRealm = regexp->realm(); + + // Step 5. If SameValue(thisRealm, oRealm) is false, throw a TypeError + // exception. + if (thisRealm != oRealm) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_REGEXP_CROSS_REALM); + return false; + } + + // Step 6. If the value of R’s [[LegacyFeaturesEnabled]] internal slot is + // false, throw a TypeError exception. + bool legacyEnabled = regexp->legacyFeaturesEnabled(); + if (!legacyEnabled) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_REGEXP_LEGACY_FEATURES_DISABLED); + return false; + } + } + + // Step 7. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] + // internal slot, then RootedValue patternValue(cx, args.get(0)); ESClass cls; if (!GetClassOfValue(cx, patternValue, &cls)) { return false; } if (cls == ESClass::RegExp) { - // Step 3a. + // Step 7.i. If flags is not undefined, throw a TypeError exception. if (args.hasDefined(1)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED); @@ -575,7 +633,8 @@ MOZ_ALWAYS_INLINE bool regexp_compile_impl(JSContext* cx, Rooted<JSAtom*> sourceAtom(cx); RegExpFlags flags = RegExpFlag::NoFlags; { - // Step 3b. + // Step 7.ii. Let P be the value of pattern’s [[OriginalSource]] internal + // slot. RegExpShared* shared = RegExpToShared(cx, patternObj); if (!shared) { return false; @@ -585,20 +644,20 @@ MOZ_ALWAYS_INLINE bool regexp_compile_impl(JSContext* cx, flags = shared->getFlags(); } - // Step 5, minus lastIndex zeroing. + // Step 9, minus lastIndex zeroing. regexp->initIgnoringLastIndex(sourceAtom, flags); } else { - // Step 4. + // Step 8. RootedValue P(cx, patternValue); RootedValue F(cx, args.get(1)); - // Step 5, minus lastIndex zeroing. + // Step 9, minus lastIndex zeroing. if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F)) { return false; } } - // The final niggling bit of step 5. + // The final niggling bit of step 8. // // |regexp| is user-exposed, so its "lastIndex" property might be // non-writable. @@ -624,15 +683,14 @@ bool js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) { AutoJSConstructorProfilerEntry pseudoFrame(cx, "RegExp"); CallArgs args = CallArgsFromVp(argc, vp); + RootedObject newTarget(cx); + // Steps 1. bool patternIsRegExp; if (!IsRegExp(cx, args.get(0), &patternIsRegExp)) { return false; } - // We can delay step 3 and step 4a until later, during - // GetPrototypeFromBuiltinConstructor calls. Accessing the new.target - // and the callee from the stack is unobservable. if (!args.isConstructing()) { // Step 3.b. if (patternIsRegExp && !args.hasDefined(1)) { @@ -652,6 +710,8 @@ bool js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) { return true; } } + } else { + newTarget = &args.newTarget().toObject(); } RootedValue patternValue(cx, args.get(0)); @@ -693,7 +753,8 @@ bool js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) { return false; } - Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto)); + Rooted<RegExpObject*> regexp( + cx, RegExpAlloc(cx, GenericObject, proto, newTarget)); if (!regexp) { return false; } @@ -769,7 +830,8 @@ bool js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) { return false; } - Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto)); + Rooted<RegExpObject*> regexp( + cx, RegExpAlloc(cx, GenericObject, proto, newTarget)); if (!regexp) { return false; } @@ -1304,11 +1366,45 @@ static bool regexp_escape(JSContext* cx, unsigned argc, Value* vp) { * RegExp.rightContext $' */ +static bool checkRegexpLegacyFeatures(JSContext* cx, const CallArgs& args, + const char* name) { + if (JS::Prefs::experimental_legacy_regexp()) { + /* Step 1. Assert C is an object that has an internal slot named + * internalSlotName.*/ + JSObject* regexpCtor = + GlobalObject::getOrCreateRegExpConstructor(cx, cx->global()); + if (!regexpCtor) return false; + + /* Step 2. If SameValue(C, thisValue) is false, throw TypeError */ + bool same = false; + if (!args.thisv().isObject() || + !SameValue(cx, args.thisv(), ObjectValue(*regexpCtor), &same) || + !same) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_RECEIVER, name, + InformalValueTypeName(args.thisv())); + return false; + } + + /* Step 4. If val is empty, throw a TypeError exception */ + RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); + if (!res) return false; + if (res->isInvalidated()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_REGEXP_STATIC_EMPTY, name, + InformalValueTypeName(args.thisv())); + return false; + } + } + return true; +} + #define DEFINE_STATIC_GETTER(name, code) \ static bool name(JSContext* cx, unsigned argc, Value* vp) { \ CallArgs args = CallArgsFromVp(argc, vp); \ RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \ if (!res) return false; \ + if (!checkRegexpLegacyFeatures(cx, args, #name)) return false; \ code; \ } @@ -1335,29 +1431,80 @@ DEFINE_STATIC_GETTER(static_paren9_getter, STATIC_PAREN_GETTER_CODE(9)) #define DEFINE_STATIC_SETTER(name, code) \ static bool name(JSContext* cx, unsigned argc, Value* vp) { \ + CallArgs args = CallArgsFromVp(argc, vp); \ RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \ if (!res) return false; \ + if (!checkRegexpLegacyFeatures(cx, args, #name)) return false; \ code; \ return true; \ } static bool static_input_setter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + if (JS::Prefs::experimental_legacy_regexp()) { + // Step 1. Assert C is an object that has an internal slot named + // internalSlotName. + JSObject* regexpCtor = + GlobalObject::getOrCreateRegExpConstructor(cx, cx->global()); + if (!regexpCtor) { + return false; + } + + // Step 2. If SameValue(C, thisValue) is false, throw a TypeError exception. + bool same = false; + if (!args.thisv().isObject() || + !SameValue(cx, args.thisv(), ObjectValue(*regexpCtor), &same) || + !same) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_RECEIVER, + InformalValueTypeName(args.thisv())); + return false; + } + } + RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); if (!res) { return false; } + // Step 3. Let strVal be ? ToString(val). RootedString str(cx, ToString<CanGC>(cx, args.get(0))); if (!str) { return false; } + // Step 4. Set the value of the internal slot of C named internalSlotName to + // strVal. res->setPendingInput(str); args.rval().setString(str); return true; } +#ifdef NIGHTLY_BUILD +const JSPropertySpec js::regexp_static_props[] = { + JS_PSGS("input", static_input_getter, static_input_setter, 0), + JS_PSG("lastMatch", static_lastMatch_getter, 0), + JS_PSG("lastParen", static_lastParen_getter, 0), + JS_PSG("leftContext", static_leftContext_getter, 0), + JS_PSG("rightContext", static_rightContext_getter, 0), + JS_PSG("$1", static_paren1_getter, 0), + JS_PSG("$2", static_paren2_getter, 0), + JS_PSG("$3", static_paren3_getter, 0), + JS_PSG("$4", static_paren4_getter, 0), + JS_PSG("$5", static_paren5_getter, 0), + JS_PSG("$6", static_paren6_getter, 0), + JS_PSG("$7", static_paren7_getter, 0), + JS_PSG("$8", static_paren8_getter, 0), + JS_PSG("$9", static_paren9_getter, 0), + JS_PSGS("$_", static_input_getter, static_input_setter, 0), + JS_PSG("$&", static_lastMatch_getter, 0), + JS_PSG("$+", static_lastParen_getter, 0), + JS_PSG("$`", static_leftContext_getter, 0), + JS_PSG("$'", static_rightContext_getter, 0), + JS_SELF_HOSTED_SYM_GET(species, "$RegExpSpecies", 0), + JS_PS_END, +}; +#else const JSPropertySpec js::regexp_static_props[] = { JS_PSGS("input", static_input_getter, static_input_setter, JSPROP_PERMANENT | JSPROP_ENUMERATE), @@ -1386,6 +1533,7 @@ const JSPropertySpec js::regexp_static_props[] = { JS_SELF_HOSTED_SYM_GET(species, "$RegExpSpecies", 0), JS_PS_END, }; +#endif const JSFunctionSpec js::regexp_static_methods[] = { JS_FN("escape", regexp_escape, 1, 0), @@ -1430,13 +1578,12 @@ static RegExpRunStatus ExecuteRegExp(JSContext* cx, HandleObject regexp, /* Steps 3, 10-14, except 12.a.i, 12.c.i.1. */ RegExpRunStatus status = - ExecuteRegExpImpl(cx, res, &re, input, lastIndex, matches); + ExecuteRegExpImpl(cx, res, &re, input, lastIndex, matches, reobj); if (status == RegExpRunStatus::Error) { return RegExpRunStatus::Error; } /* Steps 12.a.i, 12.c.i.i, 15 are done by Self-hosted function. */ - return status; } @@ -2145,6 +2292,10 @@ bool js::RegExpBuiltinExec(JSContext* cx, Handle<RegExpObject*> regexp, static_assert(JSString::MAX_LENGTH <= INT32_MAX, "lastIndex fits in int32_t"); // Steps 6, 8-35. + RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); + if (!res) { + return false; + } if (forTest) { bool result; @@ -2152,6 +2303,7 @@ bool js::RegExpBuiltinExec(JSContext* cx, Handle<RegExpObject*> regexp, &result)) { return false; } + rval.setBoolean(result); return true; } diff --git a/js/src/builtin/RegExp.h b/js/src/builtin/RegExp.h @@ -114,7 +114,8 @@ JSObject* InitRegExpClass(JSContext* cx, HandleObject obj); [[nodiscard]] extern bool RegExpCreate(JSContext* cx, HandleValue pattern, HandleValue flags, - MutableHandleValue rval); + MutableHandleValue rval, + HandleObject newTarget); [[nodiscard]] extern bool IsRegExpPrototypeOptimizable(JSContext* cx, unsigned argc, diff --git a/js/src/jit-test/tests/basic/testCrossCompartmentTransparency.js b/js/src/jit-test/tests/basic/testCrossCompartmentTransparency.js @@ -76,7 +76,6 @@ if (String.prototype.toSource) { test("new String('one')", s => String.prototype.toString.call(s)); test("new RegExp('1')", r => RegExp.prototype.exec.call(r, '1').toString()); test("new RegExp('1')", r => RegExp.prototype.test.call(r, '1')); -test("new RegExp('1')", r => RegExp.prototype.compile.call(r, '1').toString()); test("new RegExp('1')", r => assertEq("a1".search(r), 1)); test("new RegExp('1')", r => assertEq("a1".match(r)[0], '1')); test("new RegExp('1')", r => assertEq("a1".replace(r, 'A'), 'aA')); diff --git a/js/src/jit-test/tests/regexp/legacy-features-enabled.js b/js/src/jit-test/tests/regexp/legacy-features-enabled.js @@ -0,0 +1,43 @@ +// |jit-test| --enable-legacy-regexp +function testLegacyFeaturesEnabled(re, str) { + for (let i = 0; i < 100; i++){ + re.test(str); + } +} + +{ + class MyRegExp extends RegExp {} + + var re1 = /a(b)c/; + testLegacyFeaturesEnabled(re1, "abc"); + assertEq(RegExp.$1, "b"); + + var re2 = new MyRegExp("d(e)f"); + testLegacyFeaturesEnabled(re2, "def"); + + var threw = false; + try { + RegExp.$1; + } catch(e) { + threw = true; + assertEq(e instanceof TypeError, true); + } +} + +{ + var other = newGlobal(); + other.eval("var re = /x(y)z/;"); + + var re = other.re; + testLegacyFeaturesEnabled(re, "xyz"); + + var threw = false; + try { + RegExp.$1; + } catch(e) { + threw = true; + assertEq(e instanceof TypeError, true); + } + + assertEq(threw, true); +} diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp @@ -2227,6 +2227,19 @@ static void UpdateRegExpStatics(MacroAssembler& masm, Register regexp, RegExpStatics::offsetOfMatchesInput()); Address lazySourceAddress(staticsReg, RegExpStatics::offsetOfLazySource()); Address lazyIndexAddress(staticsReg, RegExpStatics::offsetOfLazyIndex()); +#ifdef DEBUG + if (JS::Prefs::experimental_legacy_regexp()) { + Label legacyFeaturesEnabled; + masm.unboxNonDouble(Address(regexp, NativeObject::getFixedSlotOffset( + RegExpObject::flagsSlot())), + temp1, JSVAL_TYPE_INT32); + masm.branchTest32(Assembler::NonZero, temp1, + Imm32(RegExpObject::LegacyFeaturesEnabledBit), + &legacyFeaturesEnabled); + masm.assumeUnreachable("Non-legacy-enabled regexp in RegExp stub"); + masm.bind(&legacyFeaturesEnabled); + } +#endif masm.guardedCallPreBarrier(pendingInputAddress, MIRType::String); masm.guardedCallPreBarrier(matchesInputAddress, MIRType::String); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp @@ -13282,7 +13282,9 @@ bool InitOptionParser(OptionParser& op) { !op.addBoolOption('\0', "enable-arraybuffer-immutable", "Enable immutable ArrayBuffers") || !op.addBoolOption('\0', "enable-iterator-chunking", - "Enable Iterator Chunking")) { + "Enable Iterator Chunking") || + !op.addBoolOption('\0', "enable-legacy-regexp", + "Enable Legacy RegExp features")) { return false; } @@ -13339,6 +13341,9 @@ bool SetGlobalOptionsPreJSInit(const OptionParser& op) { JS::Prefs::setAtStartup_experimental_symbols_as_weakmap_keys( symbolsAsWeakMapKeys); + if (op.getBoolOption("enable-legacy-regexp")) { + JS::Prefs::set_experimental_legacy_regexp(true); + } #ifdef NIGHTLY_BUILD if (op.getBoolOption("enable-async-iterator-helpers")) { JS::Prefs::setAtStartup_experimental_async_iterator_helpers(true); diff --git a/js/src/tests/non262/regress/regress-591846.js b/js/src/tests/non262/regress/regress-591846.js @@ -1,11 +1,13 @@ +// |reftest| shell-option(--enable-legacy-regexp) skip-if(release_or_beta||!xulRuntime.shell) -- legacy-regexp is not released yet, requires shell-options /* * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/licenses/publicdomain/ */ // SKIP test262 export -// Note that the values for enumerable/configurable are inconsistent with -// https://github.com/tc39/proposal-regexp-legacy-features +// This test verifies that RegExp legacy static properties match TC39's expected +// attributes: non-enumerable and configurable. +// See: https://github.com/tc39/proposal-regexp-legacy-features // Reset RegExp.leftContext to the empty string. /x/.test('x'); @@ -13,8 +15,9 @@ var d = Object.getOwnPropertyDescriptor(RegExp, "leftContext"); assertEq(d.set, undefined); assertEq(typeof d.get, "function"); -assertEq(d.enumerable, true); -assertEq(d.configurable, false); +let regexpLegacyFeatures = getPrefValue('experimental.legacy_regexp'); +assertEq(d.enumerable, !regexpLegacyFeatures); +assertEq(d.configurable, regexpLegacyFeatures); assertEq(d.get.call(RegExp), ""); reportCompare(0, 0, "ok"); diff --git a/js/src/tests/test262-update.py b/js/src/tests/test262-update.py @@ -18,7 +18,6 @@ UNSUPPORTED_FEATURES = set( [ "tail-call-optimization", "Intl.Locale-info", # Bug 1693576 - "legacy-regexp", # Bug 1306461 "source-phase-imports", "source-phase-imports-module-source", "import-defer", @@ -39,7 +38,7 @@ FEATURE_CHECK_NEEDED = { "upsert": "!Map.prototype.getOrInsertComputed", # Bug 1986668 "immutable-arraybuffer": "!ArrayBuffer.prototype.sliceToImmutable", # Bug 1952253 } -RELEASE_OR_BETA = set() +RELEASE_OR_BETA = set(["legacy-regexp"]) SHELL_OPTIONS = { "ShadowRealm": "--enable-shadow-realms", "symbols-as-weakmap-keys": "--enable-symbols-as-weakmap-keys", diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/prop-desc.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/prop-desc.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-cross-realm-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-cross-realm-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-not-regexp-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-not-regexp-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-subclass-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/index/this-subclass-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/prop-desc.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/prop-desc.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-cross-realm-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-cross-realm-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-not-regexp-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-not-regexp-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-subclass-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/input/this-subclass-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/prop-desc.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/prop-desc.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-cross-realm-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-cross-realm-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-not-regexp-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-not-regexp-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-subclass-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastMatch/this-subclass-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/prop-desc.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/prop-desc.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-cross-realm-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-cross-realm-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-not-regexp-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-not-regexp-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-subclass-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/lastParen/this-subclass-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/prop-desc.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/prop-desc.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-cross-realm-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-cross-realm-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-not-regexp-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-not-regexp-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-subclass-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/leftContext/this-subclass-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/prop-desc.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/prop-desc.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-cross-realm-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-cross-realm-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-not-regexp-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-not-regexp-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-subclass-constructor.js b/js/src/tests/test262/annexB/built-ins/RegExp/legacy-accessors/rightContext/this-subclass-constructor.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/prototype/compile/this-cross-realm-instance.js b/js/src/tests/test262/annexB/built-ins/RegExp/prototype/compile/this-cross-realm-instance.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/tests/test262/annexB/built-ins/RegExp/prototype/compile/this-subclass-instance.js b/js/src/tests/test262/annexB/built-ins/RegExp/prototype/compile/this-subclass-instance.js @@ -1,4 +1,4 @@ -// |reftest| skip -- legacy-regexp is not supported +// |reftest| skip-if(release_or_beta) -- legacy-regexp is not released yet // Copyright (C) 2020 ExE Boss. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp @@ -62,9 +62,12 @@ static_assert(RegExpFlag::UnicodeSets == REGEXP_UNICODESETS_FLAG, "self-hosted JS and /v flag bits must agree"); static_assert(RegExpFlag::Sticky == REGEXP_STICKY_FLAG, "self-hosted JS and /y flag bits must agree"); - +/* + * RegExpAlloc ( newTarget ) + * https://github.com/tc39/proposal-regexp-legacy-features?tab=readme-ov-file + */ RegExpObject* js::RegExpAlloc(JSContext* cx, NewObjectKind newKind, - HandleObject proto /* = nullptr */) { + HandleObject proto, HandleObject newTarget) { Rooted<RegExpObject*> regexp( cx, NewObjectWithClassProtoAndKind<RegExpObject>(cx, proto, newKind)); if (!regexp) { @@ -74,10 +77,34 @@ RegExpObject* js::RegExpAlloc(JSContext* cx, NewObjectKind newKind, if (!SharedShape::ensureInitialCustomShape<RegExpObject>(cx, regexp)) { return nullptr; } - + // Step 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, + // "%RegExpPrototype%", «[[RegExpMatcher]], [[OriginalSource]], + // [[OriginalFlags]], [[Realm]], [[LegacyFeaturesEnabled]]»). + // Set default newTarget if not provided + bool legacyFeaturesEnabled = false; + if (JS::Prefs::experimental_legacy_regexp()) { + // Step 2. Let thisRealm be the current Realm Record. + // Step 3. Set the value of obj’s [[Realm]] internal slot to thisRealm. + JS::Realm* thisRealm = regexp->nonCCWRealm(); + + JSObject* thisRealmRegExp = + &thisRealm->maybeGlobal()->getConstructor(JSProto_RegExp); + + // Step 4. If SameValue(newTarget, thisRealm.[[Intrinsics]].[[%RegExp%]]) is + // true, Step 4.i then Set the value of obj’s [[LegacyFeaturesEnabled]] + // internal slot to true. Step 5. Else, Step 5.i. Set the value of obj’s + // [[LegacyFeaturesEnabled]] internal slot to false. + legacyFeaturesEnabled = (!newTarget || newTarget == thisRealmRegExp); + } + regexp->setLegacyFeaturesEnabled(legacyFeaturesEnabled); + + // Step 6: Perform ! DefinePropertyOrThrow(obj, "lastIndex", + // PropertyDescriptor {[[Writable]]: true, [Enumerable]]: false, + // [[Configurable]]: false}). MOZ_ASSERT(regexp->lookupPure(cx->names().lastIndex)->slot() == RegExpObject::lastIndexSlot()); + // Step 7: Return obj. return regexp; } @@ -145,7 +172,8 @@ const JSClass RegExpObject::protoClass_ = { template <typename CharT> RegExpObject* RegExpObject::create(JSContext* cx, const CharT* chars, size_t length, RegExpFlags flags, - NewObjectKind newKind) { + NewObjectKind newKind, + HandleObject newTarget) { static_assert(std::is_same_v<CharT, char16_t>, "this code may need updating if/when CharT encodes UTF-8"); @@ -154,19 +182,21 @@ RegExpObject* RegExpObject::create(JSContext* cx, const CharT* chars, return nullptr; } - return create(cx, source, flags, newKind); + return create(cx, source, flags, newKind, newTarget); } template RegExpObject* RegExpObject::create(JSContext* cx, const char16_t* chars, size_t length, RegExpFlags flags, - NewObjectKind newKind); + NewObjectKind newKind, + HandleObject newTarget); RegExpObject* RegExpObject::createSyntaxChecked(JSContext* cx, Handle<JSAtom*> source, RegExpFlags flags, - NewObjectKind newKind) { - RegExpObject* regexp = RegExpAlloc(cx, newKind); + NewObjectKind newKind, + HandleObject newTarget) { + RegExpObject* regexp = RegExpAlloc(cx, newKind, nullptr, newTarget); if (!regexp) { return nullptr; } @@ -177,7 +207,8 @@ RegExpObject* RegExpObject::createSyntaxChecked(JSContext* cx, } RegExpObject* RegExpObject::create(JSContext* cx, Handle<JSAtom*> source, - RegExpFlags flags, NewObjectKind newKind) { + RegExpFlags flags, NewObjectKind newKind, + HandleObject newTarget) { Rooted<RegExpObject*> regexp(cx); { AutoReportFrontendContext fc(cx); @@ -190,7 +221,7 @@ RegExpObject* RegExpObject::create(JSContext* cx, Handle<JSAtom*> source, return nullptr; } - regexp = RegExpAlloc(cx, newKind); + regexp = RegExpAlloc(cx, newKind, nullptr, newTarget); if (!regexp) { return nullptr; } @@ -1088,7 +1119,9 @@ JSObject* js::CloneRegExpObject(JSContext* cx, Handle<RegExpObject*> regex) { clone->initAndZeroLastIndex(shared->getSource(), shared->getFlags(), cx); clone->setShared(shared); - + if (JS::Prefs::experimental_legacy_regexp()) { + clone->setLegacyFeaturesEnabled(regex->legacyFeaturesEnabled()); + } return clone; } diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h @@ -36,7 +36,8 @@ class GenericPrinter; class JSONPrinter; extern RegExpObject* RegExpAlloc(JSContext* cx, NewObjectKind newKind, - HandleObject proto = nullptr); + HandleObject proto = nullptr, + HandleObject newTarget = nullptr); extern JSObject* CloneRegExpObject(JSContext* cx, Handle<RegExpObject*> regex); @@ -61,23 +62,32 @@ class RegExpObject : public NativeObject { static const JSClass class_; static const JSClass protoClass_; + static const size_t RegExpFlagsMask = JS::RegExpFlag::AllFlags; + static const size_t LegacyFeaturesEnabledBit = Bit(8); + + static_assert((RegExpFlagsMask & LegacyFeaturesEnabledBit) == 0, + "LegacyFeaturesEnabledBit must not overlap"); + // The maximum number of pairs a MatchResult can have, without having to // allocate a bigger MatchResult. static const size_t MaxPairCount = 14; template <typename CharT> static RegExpObject* create(JSContext* cx, const CharT* chars, size_t length, - JS::RegExpFlags flags, NewObjectKind newKind); + JS::RegExpFlags flags, NewObjectKind newKind, + HandleObject newTarget = nullptr); // This variant assumes that the characters have already previously been // syntax checked. static RegExpObject* createSyntaxChecked(JSContext* cx, Handle<JSAtom*> source, JS::RegExpFlags flags, - NewObjectKind newKind); + NewObjectKind newKind, + HandleObject newTarget = nullptr); static RegExpObject* create(JSContext* cx, Handle<JSAtom*> source, - JS::RegExpFlags flags, NewObjectKind newKind); + JS::RegExpFlags flags, NewObjectKind newKind, + HandleObject newTarget = nullptr); /* * Compute the initial shape to associate with fresh RegExp objects, @@ -139,10 +149,35 @@ class RegExpObject : public NativeObject { } JS::RegExpFlags getFlags() const { - return JS::RegExpFlags(getFixedSlot(FLAGS_SLOT).toInt32()); + uint32_t raw = getFixedSlot(FLAGS_SLOT).toInt32(); + return JS::RegExpFlags(raw & RegExpFlagsMask); } + void setFlags(JS::RegExpFlags flags) { - setFixedSlot(FLAGS_SLOT, Int32Value(flags.value())); + uint32_t raw = getFixedSlot(FLAGS_SLOT).toInt32(); + uint32_t newValue = flags.value() | (raw & ~RegExpFlagsMask); + setFixedSlot(FLAGS_SLOT, Int32Value(newValue)); + } + + bool legacyFeaturesEnabled() const { + if (!JS::Prefs::experimental_legacy_regexp()) { + return false; + } + return (getFixedSlot(FLAGS_SLOT).toInt32() & LegacyFeaturesEnabledBit); + } + + void setLegacyFeaturesEnabled(bool enabled) { + if (!JS::Prefs::experimental_legacy_regexp()) { + return; + } + Value flagsVal = getFixedSlot(FLAGS_SLOT); + uint32_t raw = flagsVal.isInt32() ? flagsVal.toInt32() : 0; + if (enabled) { + raw |= LegacyFeaturesEnabledBit; + } else { + raw &= ~LegacyFeaturesEnabledBit; + } + setFixedSlot(FLAGS_SLOT, Int32Value(raw)); } bool hasIndices() const { return getFlags().hasIndices(); } diff --git a/js/src/vm/RegExpStatics.h b/js/src/vm/RegExpStatics.h @@ -46,6 +46,30 @@ class RegExpStatics { inline void checkInvariants(); + // Legacy RegExp static properties support. + private: + bool invalidated_ = false; + + public: + bool isInvalidated() const { + if (!JS::Prefs::experimental_legacy_regexp()) { + return false; + } + return invalidated_; + } + + inline void invalidate() { + if (JS::Prefs::experimental_legacy_regexp()) { + invalidated_ = true; + } + } + + inline void clearInvalidation() { + if (JS::Prefs::experimental_legacy_regexp()) { + invalidated_ = false; + } + } + /* * Check whether a match for pair |pairNum| occurred. If so, allocate and * store the match string in |*out|; otherwise place |undefined| in |*out|. @@ -162,6 +186,12 @@ inline bool RegExpStatics::createLastMatch(JSContext* cx, if (!executeLazy(cx)) { return false; } + + if (isInvalidated()) { + out.setUndefined(); + return true; + } + return makeMatch(cx, 0, out); } @@ -171,6 +201,11 @@ inline bool RegExpStatics::createLastParen(JSContext* cx, return false; } + if (isInvalidated()) { + out.setUndefined(); + return true; + } + if (matches.empty() || matches.pairCount() == 1) { out.setString(cx->runtime()->emptyString); return true; @@ -192,6 +227,11 @@ inline bool RegExpStatics::createParen(JSContext* cx, size_t pairNum, return false; } + if (isInvalidated()) { + out.setUndefined(); + return true; + } + if (matches.empty() || pairNum >= matches.pairCount()) { out.setString(cx->runtime()->emptyString); return true; @@ -205,6 +245,11 @@ inline bool RegExpStatics::createLeftContext(JSContext* cx, return false; } + if (isInvalidated()) { + out.setUndefined(); + return true; + } + if (matches.empty()) { out.setString(cx->runtime()->emptyString); return true; @@ -222,6 +267,11 @@ inline bool RegExpStatics::createRightContext(JSContext* cx, return false; } + if (isInvalidated()) { + out.setUndefined(); + return true; + } + if (matches.empty()) { out.setString(cx->runtime()->emptyString); return true; @@ -250,7 +300,7 @@ inline bool RegExpStatics::updateFromMatchPairs(JSContext* cx, ReportOutOfMemory(cx); return false; } - + clearInvalidation(); return true; } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp @@ -1014,7 +1014,7 @@ static bool intrinsic_RegExpCreate(JSContext* cx, unsigned argc, Value* vp) { args[1].isString() || args[1].isUndefined()); MOZ_ASSERT(!args.isConstructing()); - return RegExpCreate(cx, args[0], args.get(1), args.rval()); + return RegExpCreate(cx, args[0], args.get(1), args.rval(), nullptr); } static bool intrinsic_RegExpGetSubstitution(JSContext* cx, unsigned argc, diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml @@ -8994,6 +8994,13 @@ mirror: always set_spidermonkey_pref: startup +# Experimental support for Legacy RegExp in JavaScript. +- name: javascript.options.experimental.legacy_regexp + type: bool + value: @IS_NIGHTLY_BUILD@ + mirror: always + set_spidermonkey_pref: always + #ifdef NIGHTLY_BUILD # Experimental support for Async Iterator Helpers in JavaScript. - name: javascript.options.experimental.async_iterator_helpers