diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg index 466daf9e1b8f..1e3fc625608c 100644 --- a/js/public/friend/ErrorNumbers.msg +++ b/js/public/friend/ErrorNumbers.msg @@ -606,6 +606,7 @@ MSG_DEF(JSMSG_INVALID_CAPTURE_NAME, 0, JSEXN_SYNTAXERR, "invalid capture MSG_DEF(JSMSG_DUPLICATE_CAPTURE_NAME, 0, JSEXN_SYNTAXERR, "duplicate capture group name in regular expression") MSG_DEF(JSMSG_INVALID_NAMED_REF, 0, JSEXN_SYNTAXERR, "invalid named reference in regular expression") MSG_DEF(JSMSG_INVALID_NAMED_CAPTURE_REF, 0, JSEXN_SYNTAXERR, "invalid named capture reference in regular expression") +MSG_DEF(JSMSG_INCOMPATIBLE_REGEXP_GETTER, 2, JSEXN_TYPEERR, "RegExp.prototype.{0} getter called on non-RegExp object: {1}") // Self-hosting MSG_DEF(JSMSG_DEFAULT_LOCALE_ERROR, 0, JSEXN_ERR, "internal error getting the default locale") diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index b11eeac6b583..c47a4e617139 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -663,184 +663,133 @@ bool js::regexp_construct_raw_flags(JSContext* cx, unsigned argc, Value* vp) { return true; } -MOZ_ALWAYS_INLINE bool IsRegExpPrototype(HandleValue v, JSContext* cx) { - return (v.isObject() && - cx->global()->maybeGetRegExpPrototype() == &v.toObject()); -} - -// ES 2017 draft 21.2.5.4. -MOZ_ALWAYS_INLINE bool regexp_global_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Steps 4-6. - RegExpObject* reObj = &args.thisv().toObject().as(); - args.rval().setBoolean(reObj->global()); - return true; +// This is a specialized implementation of "UnwrapAndTypeCheckThis" for RegExp +// getters that need to return a special value for same-realm +// %RegExp.prototype%. +template +static bool RegExpGetter(JSContext* cx, CallArgs& args, const char* methodName, + Fn&& fn, + HandleValue fallbackValue = UndefinedHandleValue) { + JSObject* obj = nullptr; + if (args.thisv().isObject()) { + obj = &args.thisv().toObject(); + if (IsWrapper(obj)) { + obj = CheckedUnwrapStatic(obj); + if (!obj) { + ReportAccessDenied(cx); + return false; + } + } + } + + if (obj) { + // Step 4ff + if (obj->is()) { + return fn(&obj->as()); + } + + // Step 3.a. "If SameValue(R, %RegExp.prototype%) is true, return + // undefined." + // Or `return "(?:)"` for get RegExp.prototype.source. + if (obj == cx->global()->maybeGetRegExpPrototype()) { + args.rval().set(fallbackValue); + return true; + } + + // fall-through + } + + // Step 2. and Step 3.b. + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_REGEXP_GETTER, methodName, + InformalValueTypeName(args.thisv())); + return false; } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.5 get RegExp.prototype.global bool js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setUndefined(); + return RegExpGetter(cx, args, "global", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->global()); return true; - } - - // Steps 1-3. - return CallNonGenericMethod(cx, args); -} - -// ES 2017 draft 21.2.5.5. -MOZ_ALWAYS_INLINE bool regexp_ignoreCase_impl(JSContext* cx, - const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Steps 4-6. - RegExpObject* reObj = &args.thisv().toObject().as(); - args.rval().setBoolean(reObj->ignoreCase()); - return true; + }); } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.6 get RegExp.prototype.ignoreCase bool js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setUndefined(); + return RegExpGetter(cx, args, "ignoreCase", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->ignoreCase()); return true; - } - - // Steps 1-3. - return CallNonGenericMethod(cx, args); -} - -// ES 2017 draft 21.2.5.7. -MOZ_ALWAYS_INLINE bool regexp_multiline_impl(JSContext* cx, - const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Steps 4-6. - RegExpObject* reObj = &args.thisv().toObject().as(); - args.rval().setBoolean(reObj->multiline()); - return true; + }); } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.9 get RegExp.prototype.multiline bool js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setUndefined(); + return RegExpGetter(cx, args, "multiline", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->multiline()); return true; - } - - // Steps 1-3. - return CallNonGenericMethod(cx, args); -} - -// ES 2017 draft 21.2.5.10. -MOZ_ALWAYS_INLINE bool regexp_source_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Step 5. - RegExpObject* reObj = &args.thisv().toObject().as(); - RootedAtom src(cx, reObj->getSource()); - if (!src) { - return false; - } - - // Step 7. - JSString* str = EscapeRegExpPattern(cx, src); - if (!str) { - return false; - } - - args.rval().setString(str); - return true; + }); } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.12 get RegExp.prototype.source static bool regexp_source(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + // Step 3.a. Return "(?:)" for %RegExp.prototype%. + RootedValue fallback(cx, StringValue(cx->names().emptyRegExp)); + return RegExpGetter( + cx, args, "source", + [cx, args](RegExpObject* unwrapped) { + RootedAtom src(cx, unwrapped->getSource()); + MOZ_ASSERT(src); + // Mark potentially cross-compartment JSAtom. + cx->markAtom(src); - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setString(cx->names().emptyRegExp); - return true; - } + // Step 7. + JSAtom* escaped = EscapeRegExpPattern(cx, src); + if (!escaped) { + return false; + } - // Steps 1-4. - return CallNonGenericMethod(cx, args); -} - -// ES 2020 draft 21.2.5.3. -MOZ_ALWAYS_INLINE bool regexp_dotAll_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Steps 4-6. - RegExpObject* reObj = &args.thisv().toObject().as(); - args.rval().setBoolean(reObj->dotAll()); - return true; + args.rval().setString(escaped); + return true; + }, + fallback); } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.3 get RegExp.prototype.dotAll bool js::regexp_dotAll(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setUndefined(); + return RegExpGetter(cx, args, "dotAll", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->dotAll()); return true; - } - - // Steps 1-3. - return CallNonGenericMethod(cx, args); -} - -// ES 2017 draft 21.2.5.12. -MOZ_ALWAYS_INLINE bool regexp_sticky_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Steps 4-6. - RegExpObject* reObj = &args.thisv().toObject().as(); - args.rval().setBoolean(reObj->sticky()); - return true; + }); } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.14 get RegExp.prototype.sticky bool js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setUndefined(); + return RegExpGetter(cx, args, "sticky", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->sticky()); return true; - } - - // Steps 1-3. - return CallNonGenericMethod(cx, args); -} - -// ES 2017 draft 21.2.5.15. -MOZ_ALWAYS_INLINE bool regexp_unicode_impl(JSContext* cx, - const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - - // Steps 4-6. - RegExpObject* reObj = &args.thisv().toObject().as(); - args.rval().setBoolean(reObj->unicode()); - return true; + }); } +// ES2021 draft rev 0b3a808af87a9123890767152a26599cc8fde161 +// 21.2.5.17 get RegExp.prototype.unicode bool js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - - // Step 3.a. - if (IsRegExpPrototype(args.thisv(), cx)) { - args.rval().setUndefined(); + return RegExpGetter(cx, args, "unicode", [args](RegExpObject* unwrapped) { + args.rval().setBoolean(unwrapped->unicode()); return true; - } - - // Steps 1-3. - return CallNonGenericMethod(cx, args); + }); } const JSPropertySpec js::regexp_properties[] = { diff --git a/js/src/tests/jstests.list b/js/src/tests/jstests.list index 67a4f11d01b7..adb93a7793d5 100644 --- a/js/src/tests/jstests.list +++ b/js/src/tests/jstests.list @@ -401,16 +401,6 @@ skip script test262/annexB/built-ins/Function/createdynfn-no-line-terminator-htm # https://bugzilla.mozilla.org/show_bug.cgi?id=1462745 skip script test262/annexB/language/function-code/block-decl-nested-blocks-with-fun-decl.js -# https://bugzilla.mozilla.org/show_bug.cgi?id=1508683 -# All of these tests pass except with --more-compartments. -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/multiline/cross-realm.js -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/global/cross-realm.js -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/sticky/cross-realm.js -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/ignoreCase/cross-realm.js -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/unicode/cross-realm.js -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/dotAll/cross-realm.js -ignore-flag(--more-compartments) script test262/built-ins/RegExp/prototype/source/cross-realm.js - # https://bugzilla.mozilla.org/show_bug.cgi?id=1545038 # All of these tests pass except with --more-compartments. ignore-flag(--more-compartments) script test262/built-ins/String/prototype/valueOf/non-generic-realm.js diff --git a/js/src/tests/non262/RegExp/cross-compartment-getter.js b/js/src/tests/non262/RegExp/cross-compartment-getter.js new file mode 100644 index 000000000000..c625c6317747 --- /dev/null +++ b/js/src/tests/non262/RegExp/cross-compartment-getter.js @@ -0,0 +1,43 @@ +const otherGlobal = newGlobal({newCompartment: true}); + +let regExp = otherGlobal.eval("/a(b|c)/iy"); + +function get(name) { + const descriptor = Object.getOwnPropertyDescriptor(RegExp.prototype, name); + return descriptor.get.call(regExp); +} + +assertEq(get("flags"), "iy"); +assertEq(get("global"), false); +assertEq(get("ignoreCase"), true); +assertEq(get("multiline"), false); +assertEq(get("dotAll"), false); +assertEq(get("source"), "a(b|c)"); +assertEq(get("sticky"), true); +assertEq(get("unicode"), false); + +regExp = otherGlobal.eval("new RegExp('', 'gu')"); + +assertEq(get("flags"), "gu"); +assertEq(get("global"), true); +assertEq(get("ignoreCase"), false); +assertEq(get("multiline"), false); +assertEq(get("dotAll"), false); +assertEq(get("source"), "(?:)"); +assertEq(get("sticky"), false); +assertEq(get("unicode"), true); + +// Trigger escaping +regExp = otherGlobal.eval("new RegExp('a/b', '')"); + +assertEq(get("flags"), ""); +assertEq(get("global"), false); +assertEq(get("ignoreCase"), false); +assertEq(get("multiline"), false); +assertEq(get("dotAll"), false); +assertEq(get("source"), "a\\/b"); +assertEq(get("sticky"), false); +assertEq(get("unicode"), false); + +if (typeof reportCompare === "function") + reportCompare(true, true);