Bug 1519483: Add hasIndices to RegExpFlags r=mgaudet

Differential Revision: https://phabricator.services.mozilla.com/D107135
This commit is contained in:
Iain Ireland 2021-03-10 22:10:14 +00:00
parent 4ad23f942b
commit ca22d6b543
14 changed files with 73 additions and 19 deletions

View File

@ -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 <regular expression>.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; }

View File

@ -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)) {

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -2649,7 +2649,9 @@ template <typename Unit, class AnyCharsAccess>
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;

View File

@ -186,6 +186,9 @@ class JSAPITest {
JSAPITestString toSource(JS::RegExpFlags flags) {
JSAPITestString str;
if (flags.hasIndices()) {
str += "d";
}
if (flags.global()) {
str += "g";
}

View File

@ -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);

View File

@ -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) {

View File

@ -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") \

View File

@ -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;

View File

@ -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(); }

View File

@ -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(); }

View File

@ -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",