diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-03-20 10:05:23 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-03-20 10:07:06 +0100 |
commit | 7197b308fb97cd8ab7a972df6a3a17a7a265b594 (patch) | |
tree | e6a36f10a28710bac1b901915a643ce398580cdf /js | |
parent | 173a526ec0bedec17f35cc838b9c7d47a5ce13b7 (diff) | |
parent | a413cf72888dcb5197ba44aba547b8d496231cff (diff) | |
download | UXP-7197b308fb97cd8ab7a972df6a3a17a7a265b594.tar UXP-7197b308fb97cd8ab7a972df6a3a17a7a265b594.tar.gz UXP-7197b308fb97cd8ab7a972df6a3a17a7a265b594.tar.lz UXP-7197b308fb97cd8ab7a972df6a3a17a7a265b594.tar.xz UXP-7197b308fb97cd8ab7a972df6a3a17a7a265b594.zip |
Make RegExp.prototype return a regular object instead of an instance.
This resolves #77.
Merged remote-tracking branch 'janek/js_regexp_ordinary-object_1'
Diffstat (limited to 'js')
-rw-r--r-- | js/src/builtin/RegExp.cpp | 140 | ||||
-rw-r--r-- | js/src/gc/Allocator.cpp | 8 | ||||
-rw-r--r-- | js/src/jit-test/tests/gc/bug-1323868.js | 5 | ||||
-rw-r--r-- | js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js | 5 | ||||
-rw-r--r-- | js/src/tests/ecma_6/RegExp/prototype.js | 31 | ||||
-rw-r--r-- | js/src/tests/js1_8_5/extensions/clone-regexp.js | 1 | ||||
-rw-r--r-- | js/src/vm/RegExpObject.cpp | 13 | ||||
-rw-r--r-- | js/src/vm/RegExpObject.h | 4 | ||||
-rw-r--r-- | js/xpconnect/tests/chrome/test_xrayToJS.xul | 3 | ||||
-rw-r--r-- | js/xpconnect/tests/unit/test_xray_regexp.js | 9 | ||||
-rw-r--r-- | js/xpconnect/tests/unit/xpcshell.ini | 1 | ||||
-rw-r--r-- | js/xpconnect/wrappers/XrayWrapper.cpp | 8 |
12 files changed, 152 insertions, 76 deletions
diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 80a4bb5bd..b20f41c53 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -577,14 +577,29 @@ js::regexp_clone(JSContext* cx, unsigned argc, Value* vp) return true; } -/* ES6 draft rev32 21.2.5.4. */ +MOZ_ALWAYS_INLINE bool +IsRegExpInstanceOrPrototype(HandleValue v) +{ + if (!v.isObject()) + return false; + + return StandardProtoKeyOrNull(&v.toObject()) == JSProto_RegExp; +} + +// ES 2017 draft 21.2.5.4. MOZ_ALWAYS_INLINE bool regexp_global_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); args.rval().setBoolean(reObj->global()); return true; } @@ -592,19 +607,25 @@ regexp_global_impl(JSContext* cx, const CallArgs& args) bool js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod<IsRegExpObject, regexp_global_impl>(cx, args); + return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_global_impl>(cx, args); } -/* ES6 draft rev32 21.2.5.5. */ +// ES 2017 draft 21.2.5.5. MOZ_ALWAYS_INLINE bool regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); args.rval().setBoolean(reObj->ignoreCase()); return true; } @@ -612,19 +633,25 @@ regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args) bool js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod<IsRegExpObject, regexp_ignoreCase_impl>(cx, args); + return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_ignoreCase_impl>(cx, args); } -/* ES6 draft rev32 21.2.5.7. */ +// ES 2017 draft 21.2.5.7. MOZ_ALWAYS_INLINE bool regexp_multiline_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); args.rval().setBoolean(reObj->multiline()); return true; } @@ -632,24 +659,30 @@ regexp_multiline_impl(JSContext* cx, const CallArgs& args) bool js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod<IsRegExpObject, regexp_multiline_impl>(cx, args); + return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_multiline_impl>(cx, args); } -/* ES6 draft rev32 21.2.5.10. */ +// ES 2017 draft rev32 21.2.5.10. MOZ_ALWAYS_INLINE bool regexp_source_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); + + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setString(cx->names().emptyRegExp); + return true; + } - /* Step 5. */ + // Step 5. + Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); RootedAtom src(cx, reObj->getSource()); if (!src) return false; - /* Step 7. */ + // Step 7. RootedString str(cx, EscapeRegExpPattern(cx, src)); if (!str) return false; @@ -661,19 +694,25 @@ regexp_source_impl(JSContext* cx, const CallArgs& args) static bool regexp_source(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-4. */ + // Steps 1-4. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod<IsRegExpObject, regexp_source_impl>(cx, args); + return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_source_impl>(cx, args); } -/* ES6 draft rev32 21.2.5.12. */ +// ES 2017 draft 21.2.5.12. MOZ_ALWAYS_INLINE bool regexp_sticky_impl(JSContext* cx, const CallArgs& args) { - MOZ_ASSERT(IsRegExpObject(args.thisv())); - Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); - /* Steps 4-6. */ + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); args.rval().setBoolean(reObj->sticky()); return true; } @@ -681,27 +720,35 @@ regexp_sticky_impl(JSContext* cx, const CallArgs& args) bool js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod<IsRegExpObject, regexp_sticky_impl>(cx, args); + return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_sticky_impl>(cx, args); } -/* ES6 21.2.5.15. */ +// 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. */ - args.rval().setBoolean(args.thisv().toObject().as<RegExpObject>().unicode()); + MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv())); + + // Step 3.a. + if (!IsRegExpObject(args.thisv())) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>()); + args.rval().setBoolean(reObj->unicode()); return true; } bool js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) { - /* Steps 1-3. */ + // Steps 1-3. CallArgs args = CallArgsFromVp(argc, vp); - return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args); + return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_unicode_impl>(cx, args); } const JSPropertySpec js::regexp_properties[] = { @@ -829,25 +876,6 @@ const JSPropertySpec js::regexp_static_props[] = { JS_PS_END }; -JSObject* -js::CreateRegExpPrototype(JSContext* cx, JSProtoKey key) -{ - MOZ_ASSERT(key == JSProto_RegExp); - - Rooted<RegExpObject*> proto(cx, cx->global()->createBlankPrototype<RegExpObject>(cx)); - if (!proto) - return nullptr; - proto->NativeObject::setPrivate(nullptr); - - if (!RegExpObject::assignInitialShape(cx, proto)) - return nullptr; - - RootedAtom source(cx, cx->names().empty); - proto->initAndZeroLastIndex(source, RegExpFlag(0), cx); - - return proto; -} - template <typename CharT> static bool IsTrailSurrogateWithLeadSurrogateImpl(JSContext* cx, HandleLinearString input, size_t index) diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index 3994d5a5b..212493d86 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -39,8 +39,12 @@ js::Allocate(ExclusiveContext* cx, AllocKind kind, size_t nDynamicSlots, Initial MOZ_ASSERT_IF(nDynamicSlots != 0, clasp->isNative() || clasp->isProxy()); // Off-main-thread alloc cannot trigger GC or make runtime assertions. - if (!cx->isJSContext()) - return GCRuntime::tryNewTenuredObject<NoGC>(cx, kind, thingSize, nDynamicSlots); + if (!cx->isJSContext()) { + JSObject* obj = GCRuntime::tryNewTenuredObject<NoGC>(cx, kind, thingSize, nDynamicSlots); + if (MOZ_UNLIKELY(allowGC && !obj)) + ReportOutOfMemory(cx); + return obj; + } JSContext* ncx = cx->asJSContext(); JSRuntime* rt = ncx->runtime(); diff --git a/js/src/jit-test/tests/gc/bug-1323868.js b/js/src/jit-test/tests/gc/bug-1323868.js new file mode 100644 index 000000000..c7e8c9b08 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1323868.js @@ -0,0 +1,5 @@ +if (helperThreadCount() == 0) + quit(); +startgc(8301); +offThreadCompileScript("(({a,b,c}))"); +gcparam("maxBytes", gcparam("gcBytes")); diff --git a/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js b/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js index 1f7c7042f..998d25e2c 100644 --- a/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js +++ b/js/src/tests/ecma_5/RegExp/instance-property-storage-introspection.js @@ -40,9 +40,7 @@ function checkDataProperty(obj, p, expect, msg) // Check a bunch of "empty" regular expressions first. -var choices = [{ msg: "RegExp.prototype", - get: function() { return RegExp.prototype; } }, - { msg: "new RegExp()", +var choices = [{ msg: "new RegExp()", get: function() { return new RegExp(); } }, { msg: "/(?:)/", get: Function("return /(?:)/;") }]; @@ -55,7 +53,6 @@ function checkRegExp(r, msg, lastIndex) checkDataProperty(r, "lastIndex", expect, msg); } -checkRegExp(RegExp.prototype, "RegExp.prototype", 0); checkRegExp(new RegExp(), "new RegExp()", 0); checkRegExp(/(?:)/, "/(?:)/", 0); checkRegExp(Function("return /(?:)/;")(), 'Function("return /(?:)/;")()', 0); diff --git a/js/src/tests/ecma_6/RegExp/prototype.js b/js/src/tests/ecma_6/RegExp/prototype.js new file mode 100644 index 000000000..528142ab0 --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/prototype.js @@ -0,0 +1,31 @@ +const t = RegExp.prototype; + +const properties = "toSource,toString,compile,exec,test," + + "flags,global,ignoreCase,multiline,source,sticky,unicode," + + "constructor," + + "Symbol(Symbol.match),Symbol(Symbol.replace),Symbol(Symbol.search),Symbol(Symbol.split)"; +assertEq(Reflect.ownKeys(t).map(String).toString(), properties); + + +// Invoking getters on the prototype should not throw +function getter(name) { + return Object.getOwnPropertyDescriptor(t, name).get.call(t); +} + +assertEq(getter("flags"), ""); +assertEq(getter("global"), undefined); +assertEq(getter("ignoreCase"), undefined); +assertEq(getter("multiline"), undefined); +assertEq(getter("source"), "(?:)"); +assertEq(getter("sticky"), undefined); +assertEq(getter("unicode"), undefined); + +assertEq(t.toString(), "/(?:)/"); + +// The methods don't work with the prototype +assertThrowsInstanceOf(() => t.compile("b", "i"), TypeError); +assertThrowsInstanceOf(() => t.test("x"), TypeError); +assertThrowsInstanceOf(() => t.exec("x"), TypeError); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/js1_8_5/extensions/clone-regexp.js b/js/src/tests/js1_8_5/extensions/clone-regexp.js index 97f755785..8541dae98 100644 --- a/js/src/tests/js1_8_5/extensions/clone-regexp.js +++ b/js/src/tests/js1_8_5/extensions/clone-regexp.js @@ -22,7 +22,6 @@ function testRegExp(b, c=b) { testRegExp(RegExp("")); testRegExp(/(?:)/); testRegExp(/^(.*)$/gimy); -testRegExp(RegExp.prototype); var re = /\bx\b/gi; re.expando = true; diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 97f1163aa..e0b44e1eb 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -196,6 +196,12 @@ RegExpObject::trace(JSTracer* trc, JSObject* obj) } } +static JSObject* +CreateRegExpPrototype(JSContext* cx, JSProtoKey key) +{ + return cx->global()->createBlankPrototype(cx, &RegExpObject::protoClass_); +} + static const ClassOps RegExpObjectClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ @@ -229,6 +235,13 @@ const Class RegExpObject::class_ = { &RegExpObjectClassSpec }; +const Class RegExpObject::protoClass_ = { + js_Object_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), + JS_NULL_CLASS_OPS, + &RegExpObjectClassSpec +}; + RegExpObject* RegExpObject::create(ExclusiveContext* cx, const char16_t* chars, size_t length, RegExpFlag flags, TokenStream* tokenStream, LifoAlloc& alloc) diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index d6dde1668..dc428a973 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -79,9 +79,6 @@ RegExpAlloc(ExclusiveContext* cx, HandleObject proto = nullptr); extern JSObject* CloneRegExpObject(JSContext* cx, JSObject* regexp); -extern JSObject* -CreateRegExpPrototype(JSContext* cx, JSProtoKey key); - /* * A RegExpShared is the compiled representation of a regexp. A RegExpShared is * potentially pointed to by multiple RegExpObjects. Additionally, C++ code may @@ -411,6 +408,7 @@ class RegExpObject : public NativeObject static const unsigned PRIVATE_SLOT = 3; static const Class class_; + static const Class protoClass_; // The maximum number of pairs a MatchResult can have, without having to // allocate a bigger MatchResult. diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul index 8e6b0f8a4..aaacd4d00 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xul +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -235,8 +235,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681 gPrototypeProperties['RegExp'] = ["constructor", "toSource", "toString", "compile", "exec", "test", Symbol.match, Symbol.replace, Symbol.search, Symbol.split, - "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode", - "lastIndex"]; + "flags", "global", "ignoreCase", "multiline", "source", "sticky", "unicode"]; gConstructorProperties['RegExp'] = constructorProps(["input", "lastMatch", "lastParen", "leftContext", "rightContext", "$1", "$2", "$3", "$4", diff --git a/js/xpconnect/tests/unit/test_xray_regexp.js b/js/xpconnect/tests/unit/test_xray_regexp.js new file mode 100644 index 000000000..4baffd63e --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_regexp.js @@ -0,0 +1,9 @@ +const Cu = Components.utils; + +function run_test() { + var sandbox = Cu.Sandbox('http://www.example.com'); + var regexp = Cu.evalInSandbox("/test/i", sandbox); + equal(RegExp.prototype.toString.call(regexp), "/test/i"); + var prototype = Cu.evalInSandbox("RegExp.prototype", sandbox); + equal(typeof prototype.lastIndex, "undefined"); +} diff --git a/js/xpconnect/tests/unit/xpcshell.ini b/js/xpconnect/tests/unit/xpcshell.ini index 99d44b975..12648d3ec 100644 --- a/js/xpconnect/tests/unit/xpcshell.ini +++ b/js/xpconnect/tests/unit/xpcshell.ini @@ -133,5 +133,6 @@ head = head_watchdog.js [test_xrayed_iterator.js] [test_xray_SavedFrame.js] [test_xray_SavedFrame-02.js] +[test_xray_regexp.js] [test_resolve_dead_promise.js] [test_asyncLoadSubScriptError.js] diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index 8011d207f..48a9fdc68 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -636,10 +636,6 @@ JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, return true; } - // Handle the 'lastIndex' property for RegExp prototypes. - if (key == JSProto_RegExp && id == GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX)) - return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc); - // Grab the JSClass. We require all Xrayable classes to have a ClassSpec. const js::Class* clasp = js::GetObjectClass(target); MOZ_ASSERT(clasp->specDefined()); @@ -881,10 +877,6 @@ JSXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_CONSTRUCTOR))) return false; - // For RegExp protoypes, add the 'lastIndex' property. - if (key == JSProto_RegExp && !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX))) - return false; - // Grab the JSClass. We require all Xrayable classes to have a ClassSpec. const js::Class* clasp = js::GetObjectClass(target); MOZ_ASSERT(clasp->specDefined()); |