Bug 1055472 - Part 17: Make the Array constructor properly subclassable. (r=jorendorff, r=bhackett, r=terrence)
authorEric Faust <efaustbmo@gmail.com>
Fri, 13 Nov 2015 18:22:22 -0800
changeset 275724 d302571cd5e59eced38cef1048a46617755828ab
parent 275723 79b47f5f715a0647ebdba664acc7544bf5ca1761
child 275725 ca6084eaafbfb041a9bc081228cdb8c7e879eb38
push id29768
push usercbook@mozilla.com
push dateMon, 07 Dec 2015 13:16:29 +0000
treeherdermozilla-central@59bc3c7a83de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, bhackett, terrence
bugs1055472
milestone45.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 1055472 - Part 17: Make the Array constructor properly subclassable. (r=jorendorff, r=bhackett, r=terrence)
js/src/gc/Marking.cpp
js/src/gc/Tracer.h
js/src/jit-test/tests/baseline/arraySubclassPropertyLookup.js
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
js/src/tests/ecma_6/Class/subclassedArrayUnboxed.js
js/src/tests/ecma_6/Reflect/construct.js
js/src/vm/ObjectGroup.cpp
js/src/vm/ObjectGroup.h
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -429,25 +429,39 @@ void
 js::TraceRoot(JSTracer* trc, T* thingp, const char* name)
 {
     AssertRootMarkingPhase(trc);
     DispatchToTracer(trc, ConvertToBase(thingp), name);
 }
 
 template <typename T>
 void
+js::TraceRoot(JSTracer* trc, ReadBarriered<T>* thingp, const char* name)
+{
+    TraceRoot(trc, thingp->unsafeGet(), name);
+}
+
+template <typename T>
+void
 js::TraceNullableRoot(JSTracer* trc, T* thingp, const char* name)
 {
     AssertRootMarkingPhase(trc);
     if (InternalGCMethods<T>::isMarkableTaggedPointer(*thingp))
         DispatchToTracer(trc, ConvertToBase(thingp), name);
 }
 
 template <typename T>
 void
+js::TraceNullableRoot(JSTracer* trc, ReadBarriered<T>* thingp, const char* name)
+{
+    TraceNullableRoot(trc, thingp->unsafeGet(), name);
+}
+
+template <typename T>
+void
 js::TraceRange(JSTracer* trc, size_t len, WriteBarrieredBase<T>* vec, const char* name)
 {
     JS::AutoTracingIndex index(trc);
     for (auto i : MakeRange(len)) {
         if (InternalGCMethods<T>::isMarkable(vec[i].get()))
             DispatchToTracer(trc, ConvertToBase(vec[i].unsafeUnbarrieredForTracing()), name);
         ++index;
     }
@@ -468,17 +482,19 @@ js::TraceRootRange(JSTracer* trc, size_t
 
 // Instantiate a copy of the Tracing templates for each derived type.
 #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(type) \
     template void js::TraceEdge<type>(JSTracer*, WriteBarrieredBase<type>*, const char*); \
     template JS_PUBLIC_API(void) JS::TraceEdge<type>(JSTracer*, JS::Heap<type>*, const char*); \
     template void js::TraceManuallyBarrieredEdge<type>(JSTracer*, type*, const char*); \
     template void js::TraceWeakEdge<type>(JSTracer*, WeakRef<type>*, const char*); \
     template void js::TraceRoot<type>(JSTracer*, type*, const char*); \
+    template void js::TraceRoot<type>(JSTracer*, ReadBarriered<type>*, const char*); \
     template void js::TraceNullableRoot<type>(JSTracer*, type*, const char*); \
+    template void js::TraceNullableRoot<type>(JSTracer*, ReadBarriered<type>*, const char*); \
     template void js::TraceRange<type>(JSTracer*, size_t, WriteBarrieredBase<type>*, const char*); \
     template void js::TraceRootRange<type>(JSTracer*, size_t, type*, const char*);
 FOR_EACH_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS)
 #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
 
 template <typename T>
 void
 js::TraceManuallyBarrieredCrossCompartmentEdge(JSTracer* trc, JSObject* src, T* dst,
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -58,22 +58,30 @@ TraceEdge(JSTracer* trc, WriteBarrieredB
 
 // Trace through a "root" edge. These edges are the initial edges in the object
 // graph traversal. Root edges are asserted to only be traversed in the initial
 // phase of a GC.
 template <typename T>
 void
 TraceRoot(JSTracer* trc, T* thingp, const char* name);
 
+template <typename T>
+void
+TraceRoot(JSTracer* trc, ReadBarriered<T>* thingp, const char* name);
+
 // Idential to TraceRoot, except that this variant will not crash if |*thingp|
 // is null.
 template <typename T>
 void
 TraceNullableRoot(JSTracer* trc, T* thingp, const char* name);
 
+template <typename T>
+void
+TraceNullableRoot(JSTracer* trc, ReadBarriered<T>* thingp, const char* name);
+
 // Like TraceEdge, but for edges that do not use one of the automatic barrier
 // classes and, thus, must be treated specially for moving GC. This method is
 // separate from TraceEdge to make accidental use of such edges more obvious.
 template <typename T>
 void
 TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name);
 
 // Visits a WeakRef, but does not trace its referents. If *thingp is not marked
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/baseline/arraySubclassPropertyLookup.js
@@ -0,0 +1,17 @@
+function f(v, expected) {
+  assertEq(v.prop, expected);
+};
+
+class SubArrayA extends Array {
+}
+class SubArrayB extends Array {
+}
+SubArrayA.prototype.prop = "A";
+SubArrayB.prototype.prop = "B";
+
+var a = new SubArrayA();
+var b = new SubArrayB();
+for (let i = 0; i < 10; i++) {
+  f(a, "A");
+  f(b, "B");
+}
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3055,19 +3055,19 @@ IsArrayConstructor(const Value& v)
     // constructor would be represented as a wrapper.
     return v.isObject() &&
            v.toObject().is<JSFunction>() &&
            v.toObject().as<JSFunction>().isNative() &&
            v.toObject().as<JSFunction>().native() == ArrayConstructor;
 }
 
 static bool
-ArrayFromCallArgs(JSContext* cx, CallArgs& args)
+ArrayFromCallArgs(JSContext* cx, CallArgs& args, HandleObject proto = nullptr)
 {
-    JSObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length());
+    JSObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto);
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
@@ -3178,18 +3178,22 @@ static const JSFunctionSpec array_static
 };
 
 /* ES5 15.4.2 */
 bool
 js::ArrayConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
+    RootedObject proto(cx);
+    if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+        return false;
+
     if (args.length() != 1 || !args[0].isNumber())
-        return ArrayFromCallArgs(cx, args);
+        return ArrayFromCallArgs(cx, args, proto);
 
     uint32_t length;
     if (args[0].isInt32()) {
         int32_t i = args[0].toInt32();
         if (i < 0) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
             return false;
         }
@@ -3198,17 +3202,17 @@ js::ArrayConstructor(JSContext* cx, unsi
         double d = args[0].toDouble();
         length = ToUint32(d);
         if (d != double(length)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
             return false;
         }
     }
 
-    JSObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length);
+    JSObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length, proto);
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 JSObject*
@@ -3305,38 +3309,37 @@ EnsureNewArrayElements(ExclusiveContext*
     if (!obj->ensureElements(cx, length))
         return false;
 
     MOZ_ASSERT_IF(cap, !obj->hasDynamicElements());
 
     return true;
 }
 
-static bool
-NewArrayIsCachable(ExclusiveContext* cxArg, NewObjectKind newKind)
-{
-    return cxArg->isJSContext() && newKind == GenericObject;
-}
-
 template <uint32_t maxLength>
 static MOZ_ALWAYS_INLINE ArrayObject*
 NewArray(ExclusiveContext* cxArg, uint32_t length,
          HandleObject protoArg, NewObjectKind newKind = GenericObject)
 {
     gc::AllocKind allocKind = GuessArrayGCKind(length);
     MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
 
-    bool isCachable = NewArrayIsCachable(cxArg, newKind);
+    RootedObject proto(cxArg, protoArg);
+    if (!proto && !GetBuiltinPrototype(cxArg, JSProto_Array, &proto))
+        return nullptr;
+
+    Rooted<TaggedProto> taggedProto(cxArg, TaggedProto(proto));
+    bool isCachable = NewObjectWithTaggedProtoIsCachable(cxArg, taggedProto, newKind, &ArrayObject::class_);
     if (isCachable) {
         JSContext* cx = cxArg->asJSContext();
         JSRuntime* rt = cx->runtime();
         NewObjectCache& cache = rt->newObjectCache;
         NewObjectCache::EntryIndex entry = -1;
-        if (cache.lookupGlobal(&ArrayObject::class_, cx->global(), allocKind, &entry)) {
+        if (cache.lookupProto(&ArrayObject::class_, proto, allocKind, &entry)) {
             gc::InitialHeap heap = GetInitialHeap(newKind, &ArrayObject::class_);
             AutoSetNewObjectMetadata metadata(cx);
             JSObject* obj = cache.newObjectFromHit(cx, entry, heap);
             if (obj) {
                 /* Fixup the elements pointer and length, which may be incorrect. */
                 ArrayObject* arr = &obj->as<ArrayObject>();
                 arr->setFixedElements();
                 arr->setLength(cx, length);
@@ -3345,20 +3348,16 @@ NewArray(ExclusiveContext* cxArg, uint32
                 {
                     return nullptr;
                 }
                 return arr;
             }
         }
     }
 
-    RootedObject proto(cxArg, protoArg);
-    if (!proto && !GetBuiltinPrototype(cxArg, JSProto_Array, &proto))
-        return nullptr;
-
     RootedObjectGroup group(cxArg, ObjectGroup::defaultNewGroup(cxArg, &ArrayObject::class_,
                                                                 TaggedProto(proto)));
     if (!group)
         return nullptr;
 
     /*
      * Get a shape with zero fixed slots, regardless of the size class.
      * See JSObject::createArray.
@@ -3384,18 +3383,18 @@ NewArray(ExclusiveContext* cxArg, uint32
     }
 
     if (newKind == SingletonObject && !JSObject::setSingleton(cxArg, arr))
         return nullptr;
 
     if (isCachable) {
         NewObjectCache& cache = cxArg->asJSContext()->runtime()->newObjectCache;
         NewObjectCache::EntryIndex entry = -1;
-        cache.lookupGlobal(&ArrayObject::class_, cxArg->global(), allocKind, &entry);
-        cache.fillGlobal(entry, &ArrayObject::class_, cxArg->global(), allocKind, arr);
+        cache.lookupProto(&ArrayObject::class_, proto, allocKind, &entry);
+        cache.fillProto(entry, &ArrayObject::class_, taggedProto, allocKind, arr);
     }
 
     if (maxLength > 0 && !EnsureNewArrayElements(cxArg, arr, std::min(maxLength, length)))
         return nullptr;
 
     probes::CreateObject(cxArg, arr);
     return arr;
 }
@@ -3485,38 +3484,40 @@ js::NewDenseCopyOnWriteArray(JSContext* 
     if (!arr)
         return nullptr;
 
     probes::CreateObject(cx, arr);
     return arr;
 }
 
 // Return a new boxed or unboxed array with the specified length and allocated
-// capacity (up to maxLength), using the specified group if possible.
+// capacity (up to maxLength), using the specified group if possible. If the
+// specified group cannot be used, ensure that the created array at least has
+// the given [[Prototype]].
 template <uint32_t maxLength>
 static inline JSObject*
 NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
                     NewObjectKind newKind = GenericObject, bool forceAnalyze = false)
 {
     MOZ_ASSERT(newKind != SingletonObject);
 
     if (group->maybePreliminaryObjects())
         group->maybePreliminaryObjects()->maybeAnalyze(cx, group, forceAnalyze);
 
     if (group->shouldPreTenure() || group->maybePreliminaryObjects())
         newKind = TenuredObject;
 
+    RootedObject proto(cx, group->proto().toObject());
     if (group->maybeUnboxedLayout()) {
         if (length > UnboxedArrayObject::MaximumCapacity)
-            return NewArray<maxLength>(cx, length, nullptr, newKind);
-
+            return NewArray<maxLength>(cx, length, proto, newKind);
         return UnboxedArrayObject::create(cx, group, length, newKind, maxLength);
     }
 
-    ArrayObject* res = NewArray<maxLength>(cx, length, nullptr, newKind);
+    ArrayObject* res = NewArray<maxLength>(cx, length, proto, newKind);
     if (!res)
         return nullptr;
 
     res->setGroup(group);
 
     // If the length calculation overflowed, make sure that is marked for the
     // new group.
     if (res->length() > INT32_MAX)
@@ -3585,19 +3586,19 @@ js::NewFullyAllocatedArrayForCallingAllo
 {
     RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array));
     if (!group)
         return nullptr;
     return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind, forceAnalyze);
 }
 
 JSObject*
-js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length)
+js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto)
 {
-    RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array));
+    RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto));
     if (!group)
         return nullptr;
     return NewArrayTryUseGroup<ArrayObject::EagerAllocationMaxLength>(cx, group, length);
 }
 
 JSObject*
 js::NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
                               const Value* vp, size_t length, NewObjectKind newKind,
@@ -3647,19 +3648,20 @@ js::NewCopiedArrayTryUseGroup(ExclusiveC
     MOZ_ASSERT(result != DenseElementResult::Incomplete);
     if (result == DenseElementResult::Failure)
         return nullptr;
 
     return obj;
 }
 
 JSObject*
-js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length)
+js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
+                                           HandleObject proto /* = nullptr */)
 {
-    RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array));
+    RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto));
     if (!group)
         return nullptr;
     return NewCopiedArrayTryUseGroup(cx, group, vp, length);
 }
 
 #ifdef DEBUG
 bool
 js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -96,32 +96,33 @@ extern JSObject*
 NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, JSObject* obj, size_t length);
 
 extern JSObject*
 NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
                                                NewObjectKind newKind = GenericObject,
                                                bool forceAnalyze = false);
 
 extern JSObject*
-NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length);
+NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto);
 
 enum class ShouldUpdateTypes
 {
     Update,
     DontUpdate
 };
 
 extern JSObject*
 NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
                           const Value* vp, size_t length,
                           NewObjectKind newKind = GenericObject,
                           ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
 
 extern JSObject*
-NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length);
+NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
+                                       HandleObject proto = nullptr);
 
 /*
  * Determines whether a write to the given element on |obj| should fail because
  * |obj| is an Array with a non-writable length, and writing that element would
  * increase the length of the array.
  */
 extern bool
 WouldDefinePastNonwritableLength(HandleNativeObject obj, uint32_t index);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -684,19 +684,19 @@ void
 NewObjectCache::fillProto(EntryIndex entry, const Class* clasp, js::TaggedProto proto,
                           gc::AllocKind kind, NativeObject* obj)
 {
     MOZ_ASSERT_IF(proto.isObject(), !proto.toObject()->is<GlobalObject>());
     MOZ_ASSERT(obj->getTaggedProto() == proto);
     return fill(entry, clasp, proto.raw(), kind, obj);
 }
 
-static bool
-NewObjectWithTaggedProtoIsCachable(ExclusiveContext* cxArg, Handle<TaggedProto> proto,
-                                   NewObjectKind newKind, const Class* clasp)
+bool
+js::NewObjectWithTaggedProtoIsCachable(ExclusiveContext* cxArg, Handle<TaggedProto> proto,
+                                       NewObjectKind newKind, const Class* clasp)
 {
     return cxArg->isJSContext() &&
            proto.isObject() &&
            newKind == GenericObject &&
            clasp->isNative() &&
            !proto.toObject()->is<GlobalObject>();
 }
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1084,16 +1084,20 @@ GetInitialHeap(NewObjectKind newKind, co
 {
     if (newKind != GenericObject)
         return gc::TenuredHeap;
     if (clasp->finalize && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
+bool
+NewObjectWithTaggedProtoIsCachable(ExclusiveContext* cxArg, Handle<TaggedProto> proto,
+                                   NewObjectKind newKind, const Class* clasp);
+
 // ES6 9.1.15 GetPrototypeFromConstructor.
 extern bool
 GetPrototypeFromConstructor(JSContext* cx, js::HandleObject newTarget, js::MutableHandleObject proto);
 
 extern bool
 GetPrototypeFromCallableConstructor(JSContext* cx, const CallArgs& args, js::MutableHandleObject proto);
 
 // Specialized call for constructing |this| with a known function callee,
--- a/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
+++ b/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
@@ -7,16 +7,17 @@ function testBuiltin(builtin, ...args) {
             this.called = true;
         }
     }
 
     let instance = new inst(...args);
     assertEq(instance instanceof inst, true);
     assertEq(instance instanceof builtin, true);
     assertEq(instance.called, true);
+    return instance;
 }
 
 function testBuiltinTypedArrays() {
     let typedArrays = [Int8Array,
                        Uint8Array,
                        Uint8ClampedArray,
                        Int16Array,
                        Uint16Array,
@@ -28,16 +29,31 @@ function testBuiltinTypedArrays() {
     for (let array of typedArrays) {
         testBuiltin(array);
         testBuiltin(array, 5);
         testBuiltin(array, new array());
         testBuiltin(array, new ArrayBuffer());
     }
 }
 
+function testBuiltinArray() {
+    let argsLists = [
+        [],
+        [15],
+        [3.0],
+        ["non-length one-arg"],
+        [5, 10, 15, "these are elements"]
+    ];
+
+    for (let args of argsLists) {
+        let instance = testBuiltin(Array, ...args);
+        assertEq(Array.isArray(instance), true);
+    }
+}
+
 testBuiltin(Function);
 testBuiltin(Object);
 testBuiltin(Boolean);
 testBuiltin(Error);
 testBuiltin(EvalError);
 testBuiltin(RangeError);
 testBuiltin(ReferenceError);
 testBuiltin(SyntaxError);
@@ -54,16 +70,17 @@ testBuiltin(Map);
 testBuiltin(Set);
 testBuiltin(WeakMap);
 testBuiltin(WeakSet);
 testBuiltin(ArrayBuffer);
 testBuiltinTypedArrays();
 testBuiltin(DataView, new ArrayBuffer());
 testBuiltin(DataView, new (newGlobal().ArrayBuffer)());
 testBuiltin(String);
+testBuiltinArray();
 
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/subclassedArrayUnboxed.js
@@ -0,0 +1,29 @@
+var test = `
+
+class foo extends Array { }
+
+function testArrs(arrs) {
+    for (let arr of arrs) {
+        assertEq(Object.getPrototypeOf(arr), foo.prototype);
+    }
+}
+
+var arrs = [];
+for (var i = 0; i < 25; i++)
+    arrs.push(new foo(1));
+
+testArrs(arrs);
+
+arrs[0].nonIndexedProp = "uhoh";
+
+arrs.push(new foo(1));
+
+testArrs(arrs);
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Reflect/construct.js
+++ b/js/src/tests/ecma_6/Reflect/construct.js
@@ -96,18 +96,16 @@ for (var ctor of constructors) {
 for (var v of SOME_PRIMITIVE_VALUES.concat(nonConstructors)) {
     assertThrowsInstanceOf(() => Reflect.construct(checkNewTarget, [], v), TypeError);
 }
 
 // The builtin Array constructor uses new.target.prototype and always
 // creates a real array object.
 function someConstructor() {}
 var result = Reflect.construct(Array, [], someConstructor);
-assertEq(Reflect.getPrototypeOf(result),
-         Array.prototype, // should be someConstructor.prototype, per ES6 22.1.1.1 Array()
-        "Congratulations on implementing Array subclassing! Fix this test for +1 karma point.");
+assertEq(Reflect.getPrototypeOf(result), someConstructor.prototype);
 assertEq(result.length, 0);
 assertEq(Array.isArray(result), true);
 
 
 // For more Reflect.construct tests, see target.js and argumentsList.js.
 
 reportCompare(0, 0);
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -1340,74 +1340,107 @@ ObjectGroup::newPlainObject(ExclusiveCon
 
     return obj;
 }
 
 /////////////////////////////////////////////////////////////////////
 // ObjectGroupCompartment AllocationSiteTable
 /////////////////////////////////////////////////////////////////////
 
-struct ObjectGroupCompartment::AllocationSiteKey : public DefaultHasher<AllocationSiteKey> {
-    JSScript* script;
+struct ObjectGroupCompartment::AllocationSiteKey : public DefaultHasher<AllocationSiteKey>,
+                                                   public JS::Traceable {
+    ReadBarrieredScript script;
 
     uint32_t offset : 24;
     JSProtoKey kind : 8;
 
+    ReadBarrieredObject proto;
+
     static const uint32_t OFFSET_LIMIT = (1 << 23);
 
-    AllocationSiteKey() { mozilla::PodZero(this); }
+    AllocationSiteKey(JSScript* script_, uint32_t offset_, JSProtoKey kind_, JSObject* proto_)
+      : script(script_), offset(offset_), kind(kind_), proto(proto_)
+    {
+        MOZ_ASSERT(offset_ < OFFSET_LIMIT);
+    }
+
+    AllocationSiteKey(AllocationSiteKey&& key)
+      : script(mozilla::Move(key.script)),
+        offset(key.offset),
+        kind(key.kind),
+        proto(mozilla::Move(key.proto))
+    { }
+
+    AllocationSiteKey(const AllocationSiteKey& key)
+      : script(key.script),
+        offset(key.offset),
+        kind(key.kind),
+        proto(key.proto)
+    { }
 
     static inline uint32_t hash(AllocationSiteKey key) {
-        return uint32_t(size_t(key.script->offsetToPC(key.offset)) ^ key.kind);
+        return uint32_t(size_t(key.script->offsetToPC(key.offset)) ^ key.kind ^
+               MovableCellHasher<JSObject*>::hash(key.proto));
     }
 
     static inline bool match(const AllocationSiteKey& a, const AllocationSiteKey& b) {
-        return a.script == b.script && a.offset == b.offset && a.kind == b.kind;
+        return DefaultHasher<JSScript*>::match(a.script, b.script) &&
+               a.offset == b.offset &&
+               a.kind == b.kind &&
+               MovableCellHasher<JSObject*>::match(a.proto, b.proto);
+    }
+
+    static void trace(AllocationSiteKey* key, JSTracer* trc) {
+        TraceRoot(trc, &key->script, "AllocationSiteKey script");
+        TraceNullableRoot(trc, &key->proto, "AllocationSiteKey proto");
     }
 };
 
 /* static */ ObjectGroup*
-ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* script, jsbytecode* pc,
-                                 JSProtoKey kind)
+ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode* pc,
+                                 JSProtoKey kind, HandleObject protoArg /* = nullptr */)
 {
-    MOZ_ASSERT(!useSingletonForAllocationSite(script, pc, kind));
-
-    uint32_t offset = script->pcToOffset(pc);
+    MOZ_ASSERT(!useSingletonForAllocationSite(scriptArg, pc, kind));
+    MOZ_ASSERT_IF(protoArg, kind == JSProto_Array);
 
-    if (offset >= ObjectGroupCompartment::AllocationSiteKey::OFFSET_LIMIT)
-        return defaultNewGroup(cx, kind);
+    uint32_t offset = scriptArg->pcToOffset(pc);
 
-    ObjectGroupCompartment::AllocationSiteKey key;
-    key.script = script;
-    key.offset = offset;
-    key.kind = kind;
+    if (offset >= ObjectGroupCompartment::AllocationSiteKey::OFFSET_LIMIT) {
+        if (protoArg)
+            return defaultNewGroup(cx, GetClassForProtoKey(kind), TaggedProto(protoArg));
+        return defaultNewGroup(cx, kind);
+    }
 
     ObjectGroupCompartment::AllocationSiteTable*& table =
         cx->compartment()->objectGroups.allocationSiteTable;
 
     if (!table) {
         table = cx->new_<ObjectGroupCompartment::AllocationSiteTable>();
         if (!table || !table->init()) {
             ReportOutOfMemory(cx);
             js_delete(table);
             table = nullptr;
             return nullptr;
         }
     }
 
+    RootedScript script(cx, scriptArg);
+    RootedObject proto(cx, protoArg);
+    if (!proto && kind != JSProto_Null && !GetBuiltinPrototype(cx, kind, &proto))
+        return nullptr;
+
+    Rooted<ObjectGroupCompartment::AllocationSiteKey> key(cx,
+        ObjectGroupCompartment::AllocationSiteKey(script, offset, kind, proto));
+
     ObjectGroupCompartment::AllocationSiteTable::AddPtr p = table->lookupForAdd(key);
     if (p)
         return p->value();
 
     AutoEnterAnalysis enter(cx);
 
-    RootedObject proto(cx);
-    if (kind != JSProto_Null && !GetBuiltinPrototype(cx, kind, &proto))
-        return nullptr;
-
     Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
     ObjectGroup* res = ObjectGroupCompartment::makeGroup(cx, GetClassForProtoKey(kind), tagged,
                                                          OBJECT_FLAG_FROM_ALLOCATION_SITE);
     if (!res)
         return nullptr;
 
     if (JSOp(*pc) == JSOP_NEWOBJECT) {
         // Keep track of the preliminary objects with this group, so we can try
@@ -1442,38 +1475,39 @@ ObjectGroup::allocationSiteGroup(JSConte
 
     return res;
 }
 
 void
 ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
                                                    JSProtoKey kind, ObjectGroup* group)
 {
-    AllocationSiteKey key;
-    key.script = script;
-    key.offset = script->pcToOffset(pc);
-    key.kind = kind;
+    AllocationSiteKey key(script, script->pcToOffset(pc), kind, group->proto().toObjectOrNull());
 
     AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key);
     MOZ_RELEASE_ASSERT(p);
     allocationSiteTable->remove(p);
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (!allocationSiteTable->putNew(key, group))
             oomUnsafe.crash("Inconsistent object table");
     }
 }
 
 /* static */ ObjectGroup*
-ObjectGroup::callingAllocationSiteGroup(JSContext* cx, JSProtoKey key)
+ObjectGroup::callingAllocationSiteGroup(JSContext* cx, JSProtoKey key, HandleObject proto)
 {
+    MOZ_ASSERT_IF(proto, key == JSProto_Array);
+
     jsbytecode* pc;
     RootedScript script(cx, cx->currentScript(&pc));
     if (script)
-        return allocationSiteGroup(cx, script, pc, key);
+        return allocationSiteGroup(cx, script, pc, key, proto);
+    if (proto)
+        return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto));
     return defaultNewGroup(cx, key);
 }
 
 /* static */ bool
 ObjectGroup::setAllocationSiteObjectGroup(JSContext* cx,
                                           HandleScript script, jsbytecode* pc,
                                           HandleObject obj, bool singleton)
 {
@@ -1748,23 +1782,21 @@ ObjectGroupCompartment::sweep(FreeOp* fo
                 js_free(entry.types);
                 e.removeFront();
             }
         }
     }
 
     if (allocationSiteTable) {
         for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) {
-            AllocationSiteKey key = e.front().key();
-            bool keyDying = IsAboutToBeFinalizedUnbarriered(&key.script);
+            bool keyDying = IsAboutToBeFinalized(&e.front().mutableKey().script) ||
+                            (e.front().key().proto && IsAboutToBeFinalized(&e.front().mutableKey().proto));
             bool valDying = IsAboutToBeFinalized(&e.front().value());
             if (keyDying || valDying)
                 e.removeFront();
-            else if (key.script != e.front().key().script)
-                e.rekeyFront(key);
         }
     }
 
     sweepNewTable(defaultNewTable);
     sweepNewTable(lazyTable);
 }
 
 void
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -505,20 +505,21 @@ class ObjectGroup : public gc::TenuredCe
                                     IdValuePair* properties, size_t nproperties,
                                     NewObjectKind newKind);
 
     // Static accessors for ObjectGroupCompartment AllocationSiteTable.
 
     // Get a non-singleton group to use for objects created at the specified
     // allocation site.
     static ObjectGroup* allocationSiteGroup(JSContext* cx, JSScript* script, jsbytecode* pc,
-                                            JSProtoKey key);
+                                            JSProtoKey key, HandleObject proto = nullptr);
 
     // Get a non-singleton group to use for objects created in a JSNative call.
-    static ObjectGroup* callingAllocationSiteGroup(JSContext* cx, JSProtoKey key);
+    static ObjectGroup* callingAllocationSiteGroup(JSContext* cx, JSProtoKey key,
+                                                   HandleObject proto = nullptr);
 
     // Set the group or singleton-ness of an object created for an allocation site.
     static bool
     setAllocationSiteObjectGroup(JSContext* cx, HandleScript script, jsbytecode* pc,
                                  HandleObject obj, bool singleton);
 
     static ArrayObject* getOrFixupCopyOnWriteObject(JSContext* cx, HandleScript script,
                                                     jsbytecode* pc);