Bug 1445854 - Part 1: Make GenericCreatePrototype use protoClass_. r=jwalden
authorJason Orendorff <jorendorff@mozilla.com>
Sat, 06 Oct 2018 12:22:35 +0000
changeset 498713 ed9091a418b9393f04c82a18b03b484609958efd
parent 498712 679d009963f936849de348afb08098a0702f148d
child 498714 8de0d349f144c1464719ba93f6ff4826eefb10f6
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1445854
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1445854 - Part 1: Make GenericCreatePrototype use protoClass_. r=jwalden Originally, long ago, the builtin prototype object of a class was always an actual instance of that class. For many of the oldest classes, this is still true. Array.isArray(Array.prototype) is true; Function.prototype is callable; and so on. As it turns out, this is a bad idea. Prototypes are a lot like uninitialized objects; thus it was a common bug to have code like if (!obj->is<WidgetObject>()) { // safety check return ThrowTypeError(cx, ...); } obj->as<WidgetObject>().getWidgetPrivateData()->doThings(); // BUG This would crash when obj happened to be Widget.prototype, because that would sneak past the safety check, and then `getWidgetPrivateData()` would typically return null. Extra checks everywhere. The solution is for each builtin class to have a class_ (for instances) and a protoClass_ (for the prototype object) that share a single ClassSpec (for the benefit of the X-ray wrapper machinery). (This problem was a pain for the spec, too. The standard committee has stopped making prototype objects special in this way. The newer ones are just plain objects with no internal slots, and where possible, old stuff like Date.prototype was retroactively changed.) GenericCreatePrototype never got the memo. This patch fixes it. Differential Revision: https://phabricator.services.mozilla.com/D7666
js/src/builtin/Stream.cpp
js/src/jit-test/tests/basic/bug1445854.js
js/src/vm/GlobalObject.h
js/src/vm/SavedFrame.h
js/src/vm/SavedStacks.cpp
js/src/vm/TypedArrayObject.cpp
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -578,17 +578,17 @@ class TeeState : public NativeObject
 const Class TeeState::class_ = {
     "TeeState",
     JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
 };
 
 #define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
 const ClassSpec cls::classSpec_ = { \
     GenericCreateConstructor<cls::constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \
-    GenericCreatePrototype, \
+    GenericCreatePrototype<cls>, \
     nullptr, \
     nullptr, \
     cls##_methods, \
     cls##_properties, \
     nullptr, \
     specFlags \
 }; \
 \
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1445854.js
@@ -0,0 +1,4 @@
+load(libdir + "asserts.js");
+if (typeof ReadableStream == "function") {
+    assertThrowsInstanceOf(TypeError, () => ReadableStream.prototype.tee());
+}
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -904,28 +904,27 @@ JSObject*
 GenericCreateConstructor(JSContext* cx, JSProtoKey key)
 {
     // Note - We duplicate the trick from ClassName() so that we don't need to
     // include vm/JSAtom-inl.h here.
     PropertyName* name = (&cx->names().Null)[key];
     return GlobalObject::createConstructor(cx, ctor, name, length, kind, jitInfo);
 }
 
-inline JSObject*
+template<typename T>
+JSObject*
 GenericCreatePrototype(JSContext* cx, JSProtoKey key)
 {
-    MOZ_ASSERT(key != JSProto_Object);
-    const Class* clasp = ProtoKeyToClass(key);
-    MOZ_ASSERT(clasp);
-    JSProtoKey protoKey = InheritanceProtoKeyForStandardClass(key);
-    if (!GlobalObject::ensureConstructor(cx, cx->global(), protoKey)) {
-        return nullptr;
-    }
-    RootedObject parentProto(cx, &cx->global()->getPrototype(protoKey).toObject());
-    return GlobalObject::createBlankPrototypeInheriting(cx, clasp, parentProto);
+    static_assert(!std::is_same<T, PlainObject>::value,
+                  "creating Object.prototype is very special and isn't handled here");
+    MOZ_ASSERT(&T::class_ == ProtoKeyToClass(key),
+               "type mismatch--probably too much copy/paste in your ClassSpec");
+    MOZ_ASSERT(InheritanceProtoKeyForStandardClass(key) == JSProto_Object,
+               "subclasses (of anything but Object) can't use GenericCreatePrototype");
+    return GlobalObject::createBlankPrototype(cx, cx->global(), &T::protoClass_);
 }
 
 inline JSProtoKey
 StandardProtoKeyOrNull(const JSObject* obj)
 {
     return JSCLASS_CACHED_PROTO_KEY(obj->getClass());
 }
 
--- a/js/src/vm/SavedFrame.h
+++ b/js/src/vm/SavedFrame.h
@@ -18,16 +18,17 @@ namespace js {
 class SavedFrame : public NativeObject {
     friend class SavedStacks;
     friend struct ::JSStructuredCloneReader;
 
     static const ClassSpec      classSpec_;
 
   public:
     static const Class          class_;
+    static const Class          protoClass_;
     static const JSPropertySpec protoAccessors[];
     static const JSFunctionSpec protoFunctions[];
     static const JSFunctionSpec staticFunctions[];
 
     // Prototype methods and properties to be exposed to JS.
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
     static bool sourceProperty(JSContext* cx, unsigned argc, Value* vp);
     static bool lineProperty(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -342,20 +342,16 @@ SavedFrame::HashPolicy::match(SavedFrame
 SavedFrame::HashPolicy::rekey(Key& key, const Key& newKey)
 {
     key = newKey;
 }
 
 /* static */ bool
 SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto)
 {
-    // The only object with the SavedFrame::class_ that doesn't have a source
-    // should be the prototype.
-    proto->as<NativeObject>().setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
-
     return FreezeObject(cx, proto);
 }
 
 static const ClassOps SavedFrameClassOps = {
     nullptr,                    // addProperty
     nullptr,                    // delProperty
     nullptr,                    // enumerate
     nullptr,                    // newEnumerate
@@ -365,17 +361,17 @@ static const ClassOps SavedFrameClassOps
     nullptr,                    // call
     nullptr,                    // hasInstance
     nullptr,                    // construct
     nullptr,                    // trace
 };
 
 const ClassSpec SavedFrame::classSpec_ = {
     GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>,
-    GenericCreatePrototype,
+    GenericCreatePrototype<SavedFrame>,
     SavedFrame::staticFunctions,
     nullptr,
     SavedFrame::protoFunctions,
     SavedFrame::protoAccessors,
     SavedFrame::finishSavedFrameInit,
     ClassSpec::DontDefineConstructor
 };
 
@@ -385,16 +381,23 @@ const ClassSpec SavedFrame::classSpec_ =
     JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) |
     JSCLASS_IS_ANONYMOUS |
     JSCLASS_FOREGROUND_FINALIZE,
     &SavedFrameClassOps,
     &SavedFrame::classSpec_
 };
 
+const Class SavedFrame::protoClass_ = {
+    js_Object_str,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame),
+    JS_NULL_CLASS_OPS,
+    &SavedFrame::classSpec_
+};
+
 /* static */ const JSFunctionSpec
 SavedFrame::staticFunctions[] = {
     JS_FS_END
 };
 
 /* static */ const JSFunctionSpec
 SavedFrame::protoFunctions[] = {
     JS_FN("constructor", SavedFrame::construct, 0, 0),
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1762,37 +1762,39 @@ TypedArrayObject::staticFunctions[] = {
 };
 
 /* static */ const JSPropertySpec
 TypedArrayObject::staticProperties[] = {
     JS_SELF_HOSTED_SYM_GET(species, "TypedArraySpecies", 0),
     JS_PS_END
 };
 
+static JSObject*
+CreateSharedTypedArrayPrototype(JSContext* cx, JSProtoKey key)
+{
+    return GlobalObject::createBlankPrototype(cx,
+                                              cx->global(),
+                                              &TypedArrayObject::sharedTypedArrayPrototypeClass);
+}
+
 static const ClassSpec
 TypedArrayObjectSharedTypedArrayPrototypeClassSpec = {
     GenericCreateConstructor<TypedArrayConstructor, 0, gc::AllocKind::FUNCTION>,
-    GenericCreatePrototype,
+    CreateSharedTypedArrayPrototype,
     TypedArrayObject::staticFunctions,
     TypedArrayObject::staticProperties,
     TypedArrayObject::protoFunctions,
     TypedArrayObject::protoAccessors,
     nullptr,
     ClassSpec::DontDefineConstructor
 };
 
 /* static */ const Class
 TypedArrayObject::sharedTypedArrayPrototypeClass = {
-    // Actually ({}).toString.call(%TypedArray%.prototype) should throw,
-    // because %TypedArray%.prototype lacks the the typed array internal
-    // slots.  (It's not clear this is desirable -- particularly applied to
-    // the actual typed array prototypes, see below -- but it's what ES6
-    // draft 20140824 requires.)  But this is about as much as we can do
-    // until we implement @@toStringTag.
-    "???",
+    "TypedArrayPrototype",
     JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray),
     JS_NULL_CLASS_OPS,
     &TypedArrayObjectSharedTypedArrayPrototypeClassSpec
 };
 
 // this default implementation is only valid for integer types
 // less than 32-bits in size.
 template<typename NativeType>