1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "vm/ObjectGroup.h"
8 
9 #include "jshashutil.h"
10 #include "jsobj.h"
11 
12 #include "gc/Marking.h"
13 #include "gc/Policy.h"
14 #include "gc/StoreBuffer.h"
15 #include "gc/Zone.h"
16 #include "js/CharacterEncoding.h"
17 #include "vm/ArrayObject.h"
18 #include "vm/Shape.h"
19 #include "vm/TaggedProto.h"
20 #include "vm/UnboxedObject.h"
21 
22 #include "jsobjinlines.h"
23 
24 #include "vm/UnboxedObject-inl.h"
25 
26 using namespace js;
27 
28 using mozilla::DebugOnly;
29 using mozilla::PodZero;
30 
31 /////////////////////////////////////////////////////////////////////
32 // ObjectGroup
33 /////////////////////////////////////////////////////////////////////
34 
35 ObjectGroup::ObjectGroup(const Class* clasp, TaggedProto proto, JSCompartment* comp,
36                          ObjectGroupFlags initialFlags)
37 {
38     PodZero(this);
39 
40     /* Windows may not appear on prototype chains. */
41     MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
42     MOZ_ASSERT(JS::StringIsASCII(clasp->name));
43 
44     this->clasp_ = clasp;
45     this->proto_ = proto;
46     this->compartment_ = comp;
47     this->flags_ = initialFlags;
48 
49     setGeneration(zone()->types.generation);
50 }
51 
52 void
53 ObjectGroup::finalize(FreeOp* fop)
54 {
55     if (newScriptDontCheckGeneration())
56         newScriptDontCheckGeneration()->clear();
57     fop->delete_(newScriptDontCheckGeneration());
58     fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
59     if (maybePreliminaryObjectsDontCheckGeneration())
60         maybePreliminaryObjectsDontCheckGeneration()->clear();
61     fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
62 }
63 
64 void
65 ObjectGroup::setProtoUnchecked(TaggedProto proto)
66 {
67     proto_ = proto;
68     MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(),
69                   proto_.toObject()->isDelegate());
70 }
71 
72 void
73 ObjectGroup::setProto(TaggedProto proto)
74 {
75     MOZ_ASSERT(singleton());
76     setProtoUnchecked(proto);
77 }
78 
79 size_t
80 ObjectGroup::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
81 {
82     size_t n = 0;
83     if (TypeNewScript* newScript = newScriptDontCheckGeneration())
84         n += newScript->sizeOfIncludingThis(mallocSizeOf);
85     if (UnboxedLayout* layout = maybeUnboxedLayoutDontCheckGeneration())
86         n += layout->sizeOfIncludingThis(mallocSizeOf);
87     return n;
88 }
89 
90 void
91 ObjectGroup::setAddendum(AddendumKind kind, void* addendum, bool writeBarrier /* = true */)
92 {
93     MOZ_ASSERT(!needsSweep());
94     MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT));
95 
96     if (writeBarrier) {
97         // Manually trigger barriers if we are clearing new script or
98         // preliminary object information. Other addendums are immutable.
99         switch (addendumKind()) {
100           case Addendum_PreliminaryObjects:
101             PreliminaryObjectArrayWithTemplate::writeBarrierPre(maybePreliminaryObjects());
102             break;
103           case Addendum_NewScript:
104             TypeNewScript::writeBarrierPre(newScript());
105             break;
106           case Addendum_None:
107             break;
108           default:
109             MOZ_ASSERT(addendumKind() == kind);
110         }
111     }
112 
113     flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK;
114     flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT;
115     addendum_ = addendum;
116 }
117 
118 /* static */ bool
119 ObjectGroup::useSingletonForClone(JSFunction* fun)
120 {
121     if (!fun->isInterpreted())
122         return false;
123 
124     if (fun->isArrow())
125         return false;
126 
127     if (fun->isSingleton())
128         return false;
129 
130     /*
131      * When a function is being used as a wrapper for another function, it
132      * improves precision greatly to distinguish between different instances of
133      * the wrapper; otherwise we will conflate much of the information about
134      * the wrapped functions.
135      *
136      * An important example is the Class.create function at the core of the
137      * Prototype.js library, which looks like:
138      *
139      * var Class = {
140      *   create: function() {
141      *     return function() {
142      *       this.initialize.apply(this, arguments);
143      *     }
144      *   }
145      * };
146      *
147      * Each instance of the innermost function will have a different wrapped
148      * initialize method. We capture this, along with similar cases, by looking
149      * for short scripts which use both .apply and arguments. For such scripts,
150      * whenever creating a new instance of the function we both give that
151      * instance a singleton type and clone the underlying script.
152      */
153 
154     uint32_t begin, end;
155     if (fun->hasScript()) {
156         if (!fun->nonLazyScript()->isLikelyConstructorWrapper())
157             return false;
158         begin = fun->nonLazyScript()->sourceStart();
159         end = fun->nonLazyScript()->sourceEnd();
160     } else {
161         if (!fun->lazyScript()->isLikelyConstructorWrapper())
162             return false;
163         begin = fun->lazyScript()->begin();
164         end = fun->lazyScript()->end();
165     }
166 
167     return end - begin <= 100;
168 }
169 
170 /* static */ bool
171 ObjectGroup::useSingletonForNewObject(JSContext* cx, JSScript* script, jsbytecode* pc)
172 {
173     /*
174      * Make a heuristic guess at a use of JSOP_NEW that the constructed object
175      * should have a fresh group. We do this when the NEW is immediately
176      * followed by a simple assignment to an object's .prototype field.
177      * This is designed to catch common patterns for subclassing in JS:
178      *
179      * function Super() { ... }
180      * function Sub1() { ... }
181      * function Sub2() { ... }
182      *
183      * Sub1.prototype = new Super();
184      * Sub2.prototype = new Super();
185      *
186      * Using distinct groups for the particular prototypes of Sub1 and
187      * Sub2 lets us continue to distinguish the two subclasses and any extra
188      * properties added to those prototype objects.
189      */
190     if (script->isGenerator())
191         return false;
192     if (JSOp(*pc) != JSOP_NEW)
193         return false;
194     pc += JSOP_NEW_LENGTH;
195     if (JSOp(*pc) == JSOP_SETPROP) {
196         if (script->getName(pc) == cx->names().prototype)
197             return true;
198     }
199     return false;
200 }
201 
202 /* static */ bool
203 ObjectGroup::useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, JSProtoKey key)
204 {
205     // The return value of this method can either be tested like a boolean or
206     // passed to a NewObject method.
207     JS_STATIC_ASSERT(GenericObject == 0);
208 
209     /*
210      * Objects created outside loops in global and eval scripts should have
211      * singleton types. For now this is only done for plain objects, but not
212      * typed arrays or normal arrays.
213      */
214 
215     if (script->functionNonDelazifying() && !script->treatAsRunOnce())
216         return GenericObject;
217 
218     if (key != JSProto_Object)
219         return GenericObject;
220 
221     // All loops in the script will have a try note indicating their boundary.
222 
223     if (!script->hasTrynotes())
224         return SingletonObject;
225 
226     unsigned offset = script->pcToOffset(pc);
227 
228     JSTryNote* tn = script->trynotes()->vector;
229     JSTryNote* tnlimit = tn + script->trynotes()->length;
230     for (; tn < tnlimit; tn++) {
231         if (tn->kind != JSTRY_FOR_IN && tn->kind != JSTRY_FOR_OF && tn->kind != JSTRY_LOOP)
232             continue;
233 
234         unsigned startOffset = script->mainOffset() + tn->start;
235         unsigned endOffset = startOffset + tn->length;
236 
237         if (offset >= startOffset && offset < endOffset)
238             return GenericObject;
239     }
240 
241     return SingletonObject;
242 }
243 
244 /* static */ bool
245 ObjectGroup::useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, const Class* clasp)
246 {
247     return useSingletonForAllocationSite(script, pc, JSCLASS_CACHED_PROTO_KEY(clasp));
248 }
249 
250 /////////////////////////////////////////////////////////////////////
251 // JSObject
252 /////////////////////////////////////////////////////////////////////
253 
254 bool
255 JSObject::shouldSplicePrototype(JSContext* cx)
256 {
257     /*
258      * During bootstrapping, if inference is enabled we need to make sure not
259      * to splice a new prototype in for Function.prototype or the global
260      * object if their __proto__ had previously been set to null, as this
261      * will change the prototype for all other objects with the same type.
262      */
263     if (staticPrototype() != nullptr)
264         return false;
265     return isSingleton();
266 }
267 
268 bool
269 JSObject::splicePrototype(JSContext* cx, const Class* clasp, Handle<TaggedProto> proto)
270 {
271     MOZ_ASSERT(cx->compartment() == compartment());
272 
273     RootedObject self(cx, this);
274 
275     /*
276      * For singleton groups representing only a single JSObject, the proto
277      * can be rearranged as needed without destroying type information for
278      * the old or new types.
279      */
280     MOZ_ASSERT(self->isSingleton());
281 
282     // Windows may not appear on prototype chains.
283     MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
284 
285     if (proto.isObject() && !proto.toObject()->setDelegate(cx))
286         return false;
287 
288     // Force type instantiation when splicing lazy group.
289     RootedObjectGroup group(cx, self->getGroup(cx));
290     if (!group)
291         return false;
292     RootedObjectGroup protoGroup(cx, nullptr);
293     if (proto.isObject()) {
294         protoGroup = proto.toObject()->getGroup(cx);
295         if (!protoGroup)
296             return false;
297     }
298 
299     group->setClasp(clasp);
300     group->setProto(proto);
301     return true;
302 }
303 
304 /* static */ ObjectGroup*
305 JSObject::makeLazyGroup(JSContext* cx, HandleObject obj)
306 {
307     MOZ_ASSERT(obj->hasLazyGroup());
308     MOZ_ASSERT(cx->compartment() == obj->compartment());
309 
310     /* De-lazification of functions can GC, so we need to do it up here. */
311     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
312         RootedFunction fun(cx, &obj->as<JSFunction>());
313         if (!fun->getOrCreateScript(cx))
314             return nullptr;
315     }
316 
317     // Find flags which need to be specified immediately on the object.
318     // Don't track whether singletons are packed.
319     ObjectGroupFlags initialFlags = OBJECT_FLAG_SINGLETON | OBJECT_FLAG_NON_PACKED;
320 
321     if (obj->isIteratedSingleton())
322         initialFlags |= OBJECT_FLAG_ITERATED;
323 
324     if (obj->isIndexed())
325         initialFlags |= OBJECT_FLAG_SPARSE_INDEXES;
326 
327     if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX)
328         initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW;
329 
330     Rooted<TaggedProto> proto(cx, obj->taggedProto());
331     ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, obj->getClass(), proto,
332                                                            initialFlags);
333     if (!group)
334         return nullptr;
335 
336     AutoEnterAnalysis enter(cx);
337 
338     /* Fill in the type according to the state of this object. */
339 
340     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted())
341         group->setInterpretedFunction(&obj->as<JSFunction>());
342 
343     obj->group_ = group;
344 
345     return group;
346 }
347 
348 /* static */ bool
349 JSObject::setNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj)
350 {
351     ObjectGroup::setDefaultNewGroupUnknown(cx, clasp, obj);
352     return obj->setFlags(cx, BaseShape::NEW_GROUP_UNKNOWN);
353 }
354 
355 /////////////////////////////////////////////////////////////////////
356 // ObjectGroupCompartment NewTable
357 /////////////////////////////////////////////////////////////////////
358 
359 /*
360  * Entries for the per-compartment set of groups which are the default
361  * types to use for some prototype. An optional associated object is used which
362  * allows multiple groups to be created with the same prototype. The
363  * associated object may be a function (for types constructed with 'new') or a
364  * type descriptor (for typed objects). These entries are also used for the set
365  * of lazy groups in the compartment, which use a null associated object
366  * (though there are only a few of these per compartment).
367  */
368 struct ObjectGroupCompartment::NewEntry
369 {
370     ReadBarrieredObjectGroup group;
371 
372     // Note: This pointer is only used for equality and does not need a read barrier.
373     JSObject* associated;
374 
375     NewEntry(ObjectGroup* group, JSObject* associated)
376       : group(group), associated(associated)
377     {}
378 
379     struct Lookup {
380         const Class* clasp;
381         TaggedProto proto;
382         JSObject* associated;
383 
384         Lookup(const Class* clasp, TaggedProto proto, JSObject* associated)
385           : clasp(clasp), proto(proto), associated(associated)
386         {}
387 
388         bool hasAssocId() const {
389             return !associated || associated->zone()->hasUniqueId(associated);
390         }
391 
392         bool ensureAssocId() const {
393             uint64_t unusedId;
394             return !associated ||
395                    associated->zoneFromAnyThread()->getUniqueId(associated, &unusedId);
396         }
397 
398         uint64_t getAssocId() const {
399             return associated ? associated->zone()->getUniqueIdInfallible(associated) : 0;
400         }
401     };
402 
403     static bool hasHash(const Lookup& l) {
404         return l.proto.hasUniqueId() && l.hasAssocId();
405     }
406 
407     static bool ensureHash(const Lookup& l) {
408         return l.proto.ensureUniqueId() && l.ensureAssocId();
409     }
410 
411     static inline HashNumber hash(const Lookup& lookup) {
412         MOZ_ASSERT(lookup.proto.hasUniqueId());
413         MOZ_ASSERT(lookup.hasAssocId());
414         HashNumber hash = uintptr_t(lookup.clasp);
415         hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.proto.uniqueId());
416         hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.getAssocId());
417         return hash;
418     }
419 
420     static inline bool match(const ObjectGroupCompartment::NewEntry& key, const Lookup& lookup) {
421         TaggedProto proto = key.group.unbarrieredGet()->proto().unbarrieredGet();
422         JSObject* assoc = key.associated;
423         MOZ_ASSERT(proto.hasUniqueId());
424         MOZ_ASSERT_IF(assoc, assoc->zone()->hasUniqueId(assoc));
425         MOZ_ASSERT(lookup.proto.hasUniqueId());
426         MOZ_ASSERT(lookup.hasAssocId());
427 
428         if (lookup.clasp && key.group.unbarrieredGet()->clasp() != lookup.clasp)
429             return false;
430         if (proto.uniqueId() != lookup.proto.uniqueId())
431             return false;
432         return !assoc || assoc->zone()->getUniqueIdInfallible(assoc) == lookup.getAssocId();
433     }
434 
435     static void rekey(NewEntry& k, const NewEntry& newKey) { k = newKey; }
436 
437     bool needsSweep() {
438         return (IsAboutToBeFinalized(&group) ||
439                 (associated && IsAboutToBeFinalizedUnbarriered(&associated)));
440     }
441 };
442 
443 namespace js {
444 template <>
445 struct FallibleHashMethods<ObjectGroupCompartment::NewEntry>
446 {
447     template <typename Lookup> static bool hasHash(Lookup&& l) {
448         return ObjectGroupCompartment::NewEntry::hasHash(mozilla::Forward<Lookup>(l));
449     }
450     template <typename Lookup> static bool ensureHash(Lookup&& l) {
451         return ObjectGroupCompartment::NewEntry::ensureHash(mozilla::Forward<Lookup>(l));
452     }
453 };
454 } // namespace js
455 
456 class ObjectGroupCompartment::NewTable : public JS::WeakCache<js::GCHashSet<NewEntry, NewEntry,
457                                                                             SystemAllocPolicy>>
458 {
459     using Table = js::GCHashSet<NewEntry, NewEntry, SystemAllocPolicy>;
460     using Base = JS::WeakCache<Table>;
461 
462   public:
463     explicit NewTable(Zone* zone) : Base(zone, Table()) {}
464 };
465 
466 /* static */ ObjectGroup*
467 ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
468                              TaggedProto proto, JSObject* associated)
469 {
470     MOZ_ASSERT_IF(associated, proto.isObject());
471     MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
472 
473     // A null lookup clasp is used for 'new' groups with an associated
474     // function. The group starts out as a plain object but might mutate into an
475     // unboxed plain object.
476     MOZ_ASSERT_IF(!clasp, !!associated);
477 
478     AutoEnterAnalysis enter(cx);
479 
480     ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.defaultNewTable;
481 
482     if (!table) {
483         table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone());
484         if (!table || !table->init()) {
485             js_delete(table);
486             table = nullptr;
487             ReportOutOfMemory(cx);
488             return nullptr;
489         }
490     }
491 
492     if (associated && !associated->is<TypeDescr>()) {
493         MOZ_ASSERT(!clasp);
494         if (associated->is<JSFunction>()) {
495 
496             // Canonicalize new functions to use the original one associated with its script.
497             JSFunction* fun = &associated->as<JSFunction>();
498             if (fun->hasScript())
499                 associated = fun->nonLazyScript()->functionNonDelazifying();
500             else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin())
501                 associated = fun->lazyScript()->functionNonDelazifying();
502             else
503                 associated = nullptr;
504 
505             // If we have previously cleared the 'new' script information for this
506             // function, don't try to construct another one.
507             if (associated && associated->wasNewScriptCleared())
508                 associated = nullptr;
509 
510         } else {
511             associated = nullptr;
512         }
513 
514         if (!associated)
515             clasp = &PlainObject::class_;
516     }
517 
518     if (proto.isObject() && !proto.toObject()->isDelegate()) {
519         RootedObject protoObj(cx, proto.toObject());
520         if (!protoObj->setDelegate(cx))
521             return nullptr;
522 
523         // Objects which are prototypes of one another should be singletons, so
524         // that their type information can be tracked more precisely. Limit
525         // this group change to plain objects, to avoid issues with other types
526         // of singletons like typed arrays.
527         if (protoObj->is<PlainObject>() && !protoObj->isSingleton()) {
528             if (!JSObject::changeToSingleton(cx->asJSContext(), protoObj))
529                 return nullptr;
530         }
531     }
532 
533     ObjectGroupCompartment::NewTable::AddPtr p =
534         table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, associated));
535     if (p) {
536         ObjectGroup* group = p->group;
537         MOZ_ASSERT_IF(clasp, group->clasp() == clasp);
538         MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ ||
539                               group->clasp() == &UnboxedPlainObject::class_);
540         MOZ_ASSERT(group->proto() == proto);
541         return group;
542     }
543 
544     ObjectGroupFlags initialFlags = 0;
545     if (proto.isDynamic() || (proto.isObject() && proto.toObject()->isNewGroupUnknown()))
546         initialFlags = OBJECT_FLAG_DYNAMIC_MASK;
547 
548     Rooted<TaggedProto> protoRoot(cx, proto);
549     ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, clasp ? clasp : &PlainObject::class_,
550                                                            protoRoot, initialFlags);
551     if (!group)
552         return nullptr;
553 
554     if (!table->add(p, ObjectGroupCompartment::NewEntry(group, associated))) {
555         ReportOutOfMemory(cx);
556         return nullptr;
557     }
558 
559     if (associated) {
560         if (associated->is<JSFunction>()) {
561             if (!TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>()))
562                 return nullptr;
563         } else {
564             group->setTypeDescr(&associated->as<TypeDescr>());
565         }
566     }
567 
568     /*
569      * Some builtin objects have slotful native properties baked in at
570      * creation via the Shape::{insert,get}initialShape mechanism. Since
571      * these properties are never explicitly defined on new objects, update
572      * the type information for them here.
573      */
574 
575     const JSAtomState& names = cx->names();
576 
577     if (clasp == &RegExpObject::class_) {
578         AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type());
579     } else if (clasp == &StringObject::class_) {
580         AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type());
581     } else if (ErrorObject::isErrorClass((clasp))) {
582         AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType());
583         AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type());
584         AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type());
585         AddTypePropertyId(cx, group, nullptr, NameToId(names.stack), TypeSet::StringType());
586     }
587 
588     return group;
589 }
590 
591 /* static */ ObjectGroup*
592 ObjectGroup::lazySingletonGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto)
593 {
594     MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
595 
596     ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.lazyTable;
597 
598     if (!table) {
599         table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone());
600         if (!table || !table->init()) {
601             ReportOutOfMemory(cx);
602             js_delete(table);
603             table = nullptr;
604             return nullptr;
605         }
606     }
607 
608     ObjectGroupCompartment::NewTable::AddPtr p =
609         table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, nullptr));
610     if (p) {
611         ObjectGroup* group = p->group;
612         MOZ_ASSERT(group->lazy());
613 
614         return group;
615     }
616 
617     AutoEnterAnalysis enter(cx);
618 
619     Rooted<TaggedProto> protoRoot(cx, proto);
620     ObjectGroup* group =
621         ObjectGroupCompartment::makeGroup(cx, clasp, protoRoot,
622                                           OBJECT_FLAG_SINGLETON | OBJECT_FLAG_LAZY_SINGLETON);
623     if (!group)
624         return nullptr;
625 
626     if (!table->add(p, ObjectGroupCompartment::NewEntry(group, nullptr))) {
627         ReportOutOfMemory(cx);
628         return nullptr;
629     }
630 
631     return group;
632 }
633 
634 /* static */ void
635 ObjectGroup::setDefaultNewGroupUnknown(JSContext* cx, const Class* clasp, HandleObject obj)
636 {
637     // If the object already has a new group, mark that group as unknown.
638     ObjectGroupCompartment::NewTable* table = cx->compartment()->objectGroups.defaultNewTable;
639     if (table) {
640         Rooted<TaggedProto> taggedProto(cx, TaggedProto(obj));
641         auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, taggedProto, nullptr);
642         auto p = table->lookup(lookup);
643         if (p)
644             MarkObjectGroupUnknownProperties(cx, p->group);
645     }
646 }
647 
648 #ifdef DEBUG
649 /* static */ bool
650 ObjectGroup::hasDefaultNewGroup(JSObject* proto, const Class* clasp, ObjectGroup* group)
651 {
652     ObjectGroupCompartment::NewTable* table = proto->compartment()->objectGroups.defaultNewTable;
653 
654     if (table) {
655         auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, TaggedProto(proto), nullptr);
656         auto p = table->lookup(lookup);
657         return p && p->group == group;
658     }
659     return false;
660 }
661 #endif /* DEBUG */
662 
663 inline const Class*
664 GetClassForProtoKey(JSProtoKey key)
665 {
666     switch (key) {
667       case JSProto_Null:
668       case JSProto_Object:
669         return &PlainObject::class_;
670       case JSProto_Array:
671         return &ArrayObject::class_;
672 
673       case JSProto_Number:
674         return &NumberObject::class_;
675       case JSProto_Boolean:
676         return &BooleanObject::class_;
677       case JSProto_String:
678         return &StringObject::class_;
679       case JSProto_Symbol:
680         return &SymbolObject::class_;
681       case JSProto_RegExp:
682         return &RegExpObject::class_;
683 
684       case JSProto_Int8Array:
685       case JSProto_Uint8Array:
686       case JSProto_Int16Array:
687       case JSProto_Uint16Array:
688       case JSProto_Int32Array:
689       case JSProto_Uint32Array:
690       case JSProto_Float32Array:
691       case JSProto_Float64Array:
692       case JSProto_Uint8ClampedArray:
693         return &TypedArrayObject::classes[key - JSProto_Int8Array];
694 
695       case JSProto_ArrayBuffer:
696         return &ArrayBufferObject::class_;
697 
698       case JSProto_SharedArrayBuffer:
699         return &SharedArrayBufferObject::class_;
700 
701       case JSProto_DataView:
702         return &DataViewObject::class_;
703 
704       default:
705         MOZ_CRASH("Bad proto key");
706     }
707 }
708 
709 /* static */ ObjectGroup*
710 ObjectGroup::defaultNewGroup(JSContext* cx, JSProtoKey key)
711 {
712     RootedObject proto(cx);
713     if (key != JSProto_Null && !GetBuiltinPrototype(cx, key, &proto))
714         return nullptr;
715     return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto.get()));
716 }
717 
718 /////////////////////////////////////////////////////////////////////
719 // ObjectGroupCompartment ArrayObjectTable
720 /////////////////////////////////////////////////////////////////////
721 
722 struct ObjectGroupCompartment::ArrayObjectKey : public DefaultHasher<ArrayObjectKey>
723 {
724     TypeSet::Type type;
725 
726     ArrayObjectKey()
727       : type(TypeSet::UndefinedType())
728     {}
729 
730     explicit ArrayObjectKey(TypeSet::Type type)
731       : type(type)
732     {}
733 
734     static inline uint32_t hash(const ArrayObjectKey& v) {
735         return v.type.raw();
736     }
737 
738     static inline bool match(const ArrayObjectKey& v1, const ArrayObjectKey& v2) {
739         return v1.type == v2.type;
740     }
741 
742     bool operator==(const ArrayObjectKey& other) {
743         return type == other.type;
744     }
745 
746     bool operator!=(const ArrayObjectKey& other) {
747         return !(*this == other);
748     }
749 
750     bool needsSweep() {
751         MOZ_ASSERT(type.isUnknown() || !type.isSingleton());
752         if (!type.isUnknown() && type.isGroup()) {
753             ObjectGroup* group = type.groupNoBarrier();
754             if (IsAboutToBeFinalizedUnbarriered(&group))
755                 return true;
756             if (group != type.groupNoBarrier())
757                 type = TypeSet::ObjectType(group);
758         }
759         return false;
760     }
761 };
762 
763 static inline bool
764 NumberTypes(TypeSet::Type a, TypeSet::Type b)
765 {
766     return (a.isPrimitive(JSVAL_TYPE_INT32) || a.isPrimitive(JSVAL_TYPE_DOUBLE))
767         && (b.isPrimitive(JSVAL_TYPE_INT32) || b.isPrimitive(JSVAL_TYPE_DOUBLE));
768 }
769 
770 /*
771  * As for GetValueType, but requires object types to be non-singletons with
772  * their default prototype. These are the only values that should appear in
773  * arrays and objects whose type can be fixed.
774  */
775 static inline TypeSet::Type
776 GetValueTypeForTable(const Value& v)
777 {
778     TypeSet::Type type = TypeSet::GetValueType(v);
779     MOZ_ASSERT(!type.isSingleton());
780     return type;
781 }
782 
783 /* static */ JSObject*
784 ObjectGroup::newArrayObject(ExclusiveContext* cx,
785                             const Value* vp, size_t length,
786                             NewObjectKind newKind, NewArrayKind arrayKind)
787 {
788     MOZ_ASSERT(newKind != SingletonObject);
789 
790     // If we are making a copy on write array, don't try to adjust the group as
791     // getOrFixupCopyOnWriteObject will do this before any objects are copied
792     // from this one.
793     if (arrayKind == NewArrayKind::CopyOnWrite) {
794         ArrayObject* obj = NewDenseCopiedArray(cx, length, vp, nullptr, newKind);
795         if (!obj || !ObjectElements::MakeElementsCopyOnWrite(cx, obj))
796             return nullptr;
797         return obj;
798     }
799 
800     // Get a type which captures all the elements in the array to be created.
801     Rooted<TypeSet::Type> elementType(cx, TypeSet::UnknownType());
802     if (arrayKind != NewArrayKind::UnknownIndex && length != 0) {
803         elementType = GetValueTypeForTable(vp[0]);
804         for (unsigned i = 1; i < length; i++) {
805             TypeSet::Type ntype = GetValueTypeForTable(vp[i]);
806             if (ntype != elementType) {
807                 if (NumberTypes(elementType, ntype)) {
808                     elementType = TypeSet::DoubleType();
809                 } else {
810                     elementType = TypeSet::UnknownType();
811                     break;
812                 }
813             }
814         }
815     }
816 
817     ObjectGroupCompartment::ArrayObjectTable*& table =
818         cx->compartment()->objectGroups.arrayObjectTable;
819 
820     if (!table) {
821         table = cx->new_<ObjectGroupCompartment::ArrayObjectTable>();
822         if (!table || !table->init()) {
823             ReportOutOfMemory(cx);
824             js_delete(table);
825             table = nullptr;
826             return nullptr;
827         }
828     }
829 
830     ObjectGroupCompartment::ArrayObjectKey key(elementType);
831     DependentAddPtr<ObjectGroupCompartment::ArrayObjectTable> p(cx, *table, key);
832 
833     RootedObjectGroup group(cx);
834     if (p) {
835         group = p->value();
836     } else {
837         RootedObject proto(cx);
838         if (!GetBuiltinPrototype(cx, JSProto_Array, &proto))
839             return nullptr;
840         Rooted<TaggedProto> taggedProto(cx, TaggedProto(proto));
841         group = ObjectGroupCompartment::makeGroup(cx, &ArrayObject::class_, taggedProto);
842         if (!group)
843             return nullptr;
844 
845         AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
846 
847         if (elementType != TypeSet::UnknownType()) {
848             // Keep track of the initial objects we create with this type.
849             // If the initial ones have a consistent shape and property types, we
850             // will try to use an unboxed layout for the group.
851             PreliminaryObjectArrayWithTemplate* preliminaryObjects =
852                 cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
853             if (!preliminaryObjects)
854                 return nullptr;
855             group->setPreliminaryObjects(preliminaryObjects);
856         }
857 
858         if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
859             return nullptr;
860     }
861 
862     // The type of the elements being added will already be reflected in type
863     // information, but make sure when creating an unboxed array that the
864     // common element type is suitable for the unboxed representation.
865     ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
866     if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
867         return nullptr;
868     if (group->maybePreliminaryObjects())
869         group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
870     if (group->maybeUnboxedLayout()) {
871         switch (group->unboxedLayout().elementType()) {
872           case JSVAL_TYPE_BOOLEAN:
873             if (elementType != TypeSet::BooleanType())
874                 updateTypes = ShouldUpdateTypes::Update;
875             break;
876           case JSVAL_TYPE_INT32:
877             if (elementType != TypeSet::Int32Type())
878                 updateTypes = ShouldUpdateTypes::Update;
879             break;
880           case JSVAL_TYPE_DOUBLE:
881             if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType())
882                 updateTypes = ShouldUpdateTypes::Update;
883             break;
884           case JSVAL_TYPE_STRING:
885             if (elementType != TypeSet::StringType())
886                 updateTypes = ShouldUpdateTypes::Update;
887             break;
888           case JSVAL_TYPE_OBJECT:
889             if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked())
890                 updateTypes = ShouldUpdateTypes::Update;
891             break;
892           default:
893             MOZ_CRASH();
894         }
895     }
896 
897     return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
898 }
899 
900 // Try to change the group of |source| to match that of |target|.
901 static bool
902 GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
903 {
904     MOZ_ASSERT(source->group() != target->group());
905 
906     if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>())
907         return true;
908 
909     if (target->group()->maybePreliminaryObjects()) {
910         bool force = IsInsideNursery(source);
911         target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force);
912     }
913 
914     if (target->is<ArrayObject>()) {
915         ObjectGroup* sourceGroup = source->group();
916 
917         if (source->is<UnboxedArrayObject>()) {
918             Shape* shape = target->as<ArrayObject>().lastProperty();
919             if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape))
920                 return false;
921         } else if (source->is<ArrayObject>()) {
922             source->setGroup(target->group());
923         } else {
924             return true;
925         }
926 
927         if (sourceGroup->maybePreliminaryObjects())
928             sourceGroup->maybePreliminaryObjects()->unregisterObject(source);
929         if (target->group()->maybePreliminaryObjects())
930             target->group()->maybePreliminaryObjects()->registerNewObject(source);
931 
932         for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
933             Value v = source->as<ArrayObject>().getDenseElement(i);
934             AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
935         }
936 
937         return true;
938     }
939 
940     if (target->is<UnboxedArrayObject>()) {
941         if (!source->is<UnboxedArrayObject>())
942             return true;
943         if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32)
944             return true;
945         if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE)
946             return true;
947 
948         return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
949     }
950 
951     return true;
952 }
953 
954 static bool
955 SameGroup(JSObject* first, JSObject* second)
956 {
957     return first->group() == second->group();
958 }
959 
960 // When generating a multidimensional array of literals, such as
961 // [[1,2],[3,4],[5.5,6.5]], try to ensure that each element of the array has
962 // the same group. This is mainly important when the elements might have
963 // different native vs. unboxed layouts, or different unboxed layouts, and
964 // accessing the heterogenous layouts from JIT code will be much slower than
965 // if they were homogenous.
966 //
967 // To do this, with each new array element we compare it with one of the
968 // previous ones, and try to mutate the group of the new element to fit that
969 // of the old element. If this isn't possible, the groups for all old elements
970 // are mutated to fit that of the new element.
971 bool
972 js::CombineArrayElementTypes(ExclusiveContext* cx, JSObject* newObj,
973                              const Value* compare, size_t ncompare)
974 {
975     if (!ncompare || !compare[0].isObject())
976         return true;
977 
978     JSObject* oldObj = &compare[0].toObject();
979     if (SameGroup(oldObj, newObj))
980         return true;
981 
982     if (!GiveObjectGroup(cx, newObj, oldObj))
983         return false;
984 
985     if (SameGroup(oldObj, newObj))
986         return true;
987 
988     if (!GiveObjectGroup(cx, oldObj, newObj))
989         return false;
990 
991     if (SameGroup(oldObj, newObj)) {
992         for (size_t i = 1; i < ncompare; i++) {
993             if (compare[i].isObject() && !SameGroup(&compare[i].toObject(), newObj)) {
994                 if (!GiveObjectGroup(cx, &compare[i].toObject(), newObj))
995                     return false;
996             }
997         }
998     }
999 
1000     return true;
1001 }
1002 
1003 // Similarly to CombineArrayElementTypes, if we are generating an array of
1004 // plain objects with a consistent property layout, such as
1005 // [{p:[1,2]},{p:[3,4]},{p:[5.5,6.5]}], where those plain objects in
1006 // turn have arrays as their own properties, try to ensure that a consistent
1007 // group is given to each array held by the same property of the plain objects.
1008 bool
1009 js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj,
1010                                     const Value* compare, size_t ncompare)
1011 {
1012     if (!ncompare || !compare[0].isObject())
1013         return true;
1014 
1015     JSObject* oldObj = &compare[0].toObject();
1016     if (!SameGroup(oldObj, newObj))
1017         return true;
1018 
1019     if (newObj->is<PlainObject>()) {
1020         if (newObj->as<PlainObject>().lastProperty() != oldObj->as<PlainObject>().lastProperty())
1021             return true;
1022 
1023         for (size_t slot = 0; slot < newObj->as<PlainObject>().slotSpan(); slot++) {
1024             Value newValue = newObj->as<PlainObject>().getSlot(slot);
1025             Value oldValue = oldObj->as<PlainObject>().getSlot(slot);
1026 
1027             if (!newValue.isObject() || !oldValue.isObject())
1028                 continue;
1029 
1030             JSObject* newInnerObj = &newValue.toObject();
1031             JSObject* oldInnerObj = &oldValue.toObject();
1032 
1033             if (SameGroup(oldInnerObj, newInnerObj))
1034                 continue;
1035 
1036             if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
1037                 return false;
1038 
1039             if (SameGroup(oldInnerObj, newInnerObj))
1040                 continue;
1041 
1042             if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
1043                 return false;
1044 
1045             if (SameGroup(oldInnerObj, newInnerObj)) {
1046                 for (size_t i = 1; i < ncompare; i++) {
1047                     if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
1048                         Value otherValue = compare[i].toObject().as<PlainObject>().getSlot(slot);
1049                         if (otherValue.isObject() && !SameGroup(&otherValue.toObject(), newInnerObj)) {
1050                             if (!GiveObjectGroup(cx, &otherValue.toObject(), newInnerObj))
1051                                 return false;
1052                         }
1053                     }
1054                 }
1055             }
1056         }
1057     } else if (newObj->is<UnboxedPlainObject>()) {
1058         const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout();
1059         const int32_t* traceList = layout.traceList();
1060         if (!traceList)
1061             return true;
1062 
1063         uint8_t* newData = newObj->as<UnboxedPlainObject>().data();
1064         uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data();
1065 
1066         for (; *traceList != -1; traceList++) {}
1067         traceList++;
1068         for (; *traceList != -1; traceList++) {
1069             JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList);
1070             JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList);
1071 
1072             if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj))
1073                 continue;
1074 
1075             if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
1076                 return false;
1077 
1078             if (SameGroup(oldInnerObj, newInnerObj))
1079                 continue;
1080 
1081             if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
1082                 return false;
1083 
1084             if (SameGroup(oldInnerObj, newInnerObj)) {
1085                 for (size_t i = 1; i < ncompare; i++) {
1086                     if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
1087                         uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data();
1088                         JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList);
1089                         if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) {
1090                             if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj))
1091                                 return false;
1092                         }
1093                     }
1094                 }
1095             }
1096         }
1097     }
1098 
1099     return true;
1100 }
1101 
1102 /////////////////////////////////////////////////////////////////////
1103 // ObjectGroupCompartment PlainObjectTable
1104 /////////////////////////////////////////////////////////////////////
1105 
1106 struct ObjectGroupCompartment::PlainObjectKey
1107 {
1108     jsid* properties;
1109     uint32_t nproperties;
1110 
1111     struct Lookup {
1112         IdValuePair* properties;
1113         uint32_t nproperties;
1114 
1115         Lookup(IdValuePair* properties, uint32_t nproperties)
1116           : properties(properties), nproperties(nproperties)
1117         {}
1118     };
1119 
1120     static inline HashNumber hash(const Lookup& lookup) {
1121         return (HashNumber) (HashId(lookup.properties[lookup.nproperties - 1].id) ^
1122                              lookup.nproperties);
1123     }
1124 
1125     static inline bool match(const PlainObjectKey& v, const Lookup& lookup) {
1126         if (lookup.nproperties != v.nproperties)
1127             return false;
1128         for (size_t i = 0; i < lookup.nproperties; i++) {
1129             if (lookup.properties[i].id != v.properties[i])
1130                 return false;
1131         }
1132         return true;
1133     }
1134 
1135     bool needsSweep() {
1136         for (unsigned i = 0; i < nproperties; i++) {
1137             if (gc::IsAboutToBeFinalizedUnbarriered(&properties[i]))
1138                 return true;
1139         }
1140         return false;
1141     }
1142 };
1143 
1144 struct ObjectGroupCompartment::PlainObjectEntry
1145 {
1146     ReadBarrieredObjectGroup group;
1147     ReadBarrieredShape shape;
1148     TypeSet::Type* types;
1149 
1150     bool needsSweep(unsigned nproperties) {
1151         if (IsAboutToBeFinalized(&group))
1152             return true;
1153         if (IsAboutToBeFinalized(&shape))
1154             return true;
1155         for (unsigned i = 0; i < nproperties; i++) {
1156             MOZ_ASSERT(!types[i].isSingleton());
1157             if (types[i].isGroup()) {
1158                 ObjectGroup* group = types[i].groupNoBarrier();
1159                 if (IsAboutToBeFinalizedUnbarriered(&group))
1160                     return true;
1161                 if (group != types[i].groupNoBarrier())
1162                     types[i] = TypeSet::ObjectType(group);
1163             }
1164         }
1165         return false;
1166     }
1167 };
1168 
1169 static bool
1170 CanShareObjectGroup(IdValuePair* properties, size_t nproperties)
1171 {
1172     // Don't reuse groups for objects containing indexed properties, which
1173     // might end up as dense elements.
1174     for (size_t i = 0; i < nproperties; i++) {
1175         uint32_t index;
1176         if (IdIsIndex(properties[i].id, &index))
1177             return false;
1178     }
1179     return true;
1180 }
1181 
1182 static bool
1183 AddPlainObjectProperties(ExclusiveContext* cx, HandlePlainObject obj,
1184                          IdValuePair* properties, size_t nproperties)
1185 {
1186     RootedId propid(cx);
1187     RootedValue value(cx);
1188 
1189     for (size_t i = 0; i < nproperties; i++) {
1190         propid = properties[i].id;
1191         value = properties[i].value;
1192         if (!NativeDefineProperty(cx, obj, propid, value, nullptr, nullptr, JSPROP_ENUMERATE))
1193             return false;
1194     }
1195 
1196     return true;
1197 }
1198 
1199 PlainObject*
1200 js::NewPlainObjectWithProperties(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties,
1201                                  NewObjectKind newKind)
1202 {
1203     gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
1204     RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind));
1205     if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties))
1206         return nullptr;
1207     return obj;
1208 }
1209 
1210 /* static */ JSObject*
1211 ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties,
1212                             NewObjectKind newKind)
1213 {
1214     // Watch for simple cases where we don't try to reuse plain object groups.
1215     if (newKind == SingletonObject || nproperties == 0 || nproperties >= PropertyTree::MAX_HEIGHT)
1216         return NewPlainObjectWithProperties(cx, properties, nproperties, newKind);
1217 
1218     ObjectGroupCompartment::PlainObjectTable*& table =
1219         cx->compartment()->objectGroups.plainObjectTable;
1220 
1221     if (!table) {
1222         table = cx->new_<ObjectGroupCompartment::PlainObjectTable>();
1223         if (!table || !table->init()) {
1224             ReportOutOfMemory(cx);
1225             js_delete(table);
1226             table = nullptr;
1227             return nullptr;
1228         }
1229     }
1230 
1231     ObjectGroupCompartment::PlainObjectKey::Lookup lookup(properties, nproperties);
1232     ObjectGroupCompartment::PlainObjectTable::Ptr p = table->lookup(lookup);
1233 
1234     if (!p) {
1235         if (!CanShareObjectGroup(properties, nproperties))
1236             return NewPlainObjectWithProperties(cx, properties, nproperties, newKind);
1237 
1238         RootedObject proto(cx);
1239         if (!GetBuiltinPrototype(cx, JSProto_Object, &proto))
1240             return nullptr;
1241 
1242         Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
1243         RootedObjectGroup group(cx, ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_,
1244                                                                       tagged));
1245         if (!group)
1246             return nullptr;
1247 
1248         gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
1249         RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group,
1250                                                                   allocKind, TenuredObject));
1251         if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties))
1252             return nullptr;
1253 
1254         // Don't make entries with duplicate property names, which will show up
1255         // here as objects with fewer properties than we thought we were
1256         // adding to the object. In this case, reset the object's group to the
1257         // default (which will have unknown properties) so that the group we
1258         // just created will be collected by the GC.
1259         if (obj->slotSpan() != nproperties) {
1260             ObjectGroup* group = defaultNewGroup(cx, obj->getClass(), obj->taggedProto());
1261             if (!group)
1262                 return nullptr;
1263             obj->setGroup(group);
1264             return obj;
1265         }
1266 
1267         // Keep track of the initial objects we create with this type.
1268         // If the initial ones have a consistent shape and property types, we
1269         // will try to use an unboxed layout for the group.
1270         PreliminaryObjectArrayWithTemplate* preliminaryObjects =
1271             cx->new_<PreliminaryObjectArrayWithTemplate>(obj->lastProperty());
1272         if (!preliminaryObjects)
1273             return nullptr;
1274         group->setPreliminaryObjects(preliminaryObjects);
1275         preliminaryObjects->registerNewObject(obj);
1276 
1277         ScopedJSFreePtr<jsid> ids(group->zone()->pod_calloc<jsid>(nproperties));
1278         if (!ids) {
1279             ReportOutOfMemory(cx);
1280             return nullptr;
1281         }
1282 
1283         ScopedJSFreePtr<TypeSet::Type> types(
1284             group->zone()->pod_calloc<TypeSet::Type>(nproperties));
1285         if (!types) {
1286             ReportOutOfMemory(cx);
1287             return nullptr;
1288         }
1289 
1290         for (size_t i = 0; i < nproperties; i++) {
1291             ids[i] = properties[i].id;
1292             types[i] = GetValueTypeForTable(obj->getSlot(i));
1293             AddTypePropertyId(cx, group, nullptr, IdToTypeId(ids[i]), types[i]);
1294         }
1295 
1296         ObjectGroupCompartment::PlainObjectKey key;
1297         key.properties = ids;
1298         key.nproperties = nproperties;
1299         MOZ_ASSERT(ObjectGroupCompartment::PlainObjectKey::match(key, lookup));
1300 
1301         ObjectGroupCompartment::PlainObjectEntry entry;
1302         entry.group.set(group);
1303         entry.shape.set(obj->lastProperty());
1304         entry.types = types;
1305 
1306         ObjectGroupCompartment::PlainObjectTable::AddPtr np = table->lookupForAdd(lookup);
1307         if (!table->add(np, key, entry)) {
1308             ReportOutOfMemory(cx);
1309             return nullptr;
1310         }
1311 
1312         ids.forget();
1313         types.forget();
1314 
1315         return obj;
1316     }
1317 
1318     RootedObjectGroup group(cx, p->value().group);
1319 
1320     // Watch for existing groups which now use an unboxed layout.
1321     if (group->maybeUnboxedLayout()) {
1322         MOZ_ASSERT(group->unboxedLayout().properties().length() == nproperties);
1323         return UnboxedPlainObject::createWithProperties(cx, group, newKind, properties);
1324     }
1325 
1326     // Update property types according to the properties we are about to add.
1327     // Do this before we do anything which can GC, which might move or remove
1328     // this table entry.
1329     if (!group->unknownProperties()) {
1330         for (size_t i = 0; i < nproperties; i++) {
1331             TypeSet::Type type = p->value().types[i];
1332             TypeSet::Type ntype = GetValueTypeForTable(properties[i].value);
1333             if (ntype == type)
1334                 continue;
1335             if (ntype.isPrimitive(JSVAL_TYPE_INT32) &&
1336                 type.isPrimitive(JSVAL_TYPE_DOUBLE))
1337             {
1338                 // The property types already reflect 'int32'.
1339             } else {
1340                 if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) &&
1341                     type.isPrimitive(JSVAL_TYPE_INT32))
1342                 {
1343                     // Include 'double' in the property types to avoid the update below later.
1344                     p->value().types[i] = TypeSet::DoubleType();
1345                 }
1346                 AddTypePropertyId(cx, group, nullptr, IdToTypeId(properties[i].id), ntype);
1347             }
1348         }
1349     }
1350 
1351     RootedShape shape(cx, p->value().shape);
1352 
1353     if (group->maybePreliminaryObjects())
1354         newKind = TenuredObject;
1355 
1356     gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
1357     RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group, allocKind,
1358                                                               newKind));
1359 
1360     if (!obj || !obj->setLastProperty(cx, shape))
1361         return nullptr;
1362 
1363     for (size_t i = 0; i < nproperties; i++)
1364         obj->setSlot(i, properties[i].value);
1365 
1366     if (group->maybePreliminaryObjects()) {
1367         group->maybePreliminaryObjects()->registerNewObject(obj);
1368         group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
1369     }
1370 
1371     return obj;
1372 }
1373 
1374 /////////////////////////////////////////////////////////////////////
1375 // ObjectGroupCompartment AllocationSiteTable
1376 /////////////////////////////////////////////////////////////////////
1377 
1378 struct ObjectGroupCompartment::AllocationSiteKey : public DefaultHasher<AllocationSiteKey> {
1379     ReadBarrieredScript script;
1380 
1381     uint32_t offset : 24;
1382     JSProtoKey kind : 8;
1383 
1384     ReadBarrieredObject proto;
1385 
1386     static const uint32_t OFFSET_LIMIT = (1 << 23);
1387 
1388     AllocationSiteKey(JSScript* script_, uint32_t offset_, JSProtoKey kind_, JSObject* proto_)
1389       : script(script_), offset(offset_), kind(kind_), proto(proto_)
1390     {
1391         MOZ_ASSERT(offset_ < OFFSET_LIMIT);
1392     }
1393 
1394     AllocationSiteKey(const AllocationSiteKey& key)
1395       : script(key.script),
1396         offset(key.offset),
1397         kind(key.kind),
1398         proto(key.proto)
1399     { }
1400 
1401     AllocationSiteKey(AllocationSiteKey&& key)
1402       : script(mozilla::Move(key.script)),
1403         offset(key.offset),
1404         kind(key.kind),
1405         proto(mozilla::Move(key.proto))
1406     { }
1407 
1408     void operator=(AllocationSiteKey&& key) {
1409         script = mozilla::Move(key.script);
1410         offset = key.offset;
1411         kind = key.kind;
1412         proto = mozilla::Move(key.proto);
1413     }
1414 
1415     static inline uint32_t hash(AllocationSiteKey key) {
1416         return uint32_t(size_t(key.script->offsetToPC(key.offset)) ^ key.kind ^
1417                MovableCellHasher<JSObject*>::hash(key.proto));
1418     }
1419 
1420     static inline bool match(const AllocationSiteKey& a, const AllocationSiteKey& b) {
1421         return DefaultHasher<JSScript*>::match(a.script, b.script) &&
1422                a.offset == b.offset &&
1423                a.kind == b.kind &&
1424                MovableCellHasher<JSObject*>::match(a.proto, b.proto);
1425     }
1426 
1427     void trace(JSTracer* trc) {
1428         TraceRoot(trc, &script, "AllocationSiteKey script");
1429         TraceNullableRoot(trc, &proto, "AllocationSiteKey proto");
1430     }
1431 
1432     bool needsSweep() {
1433         return IsAboutToBeFinalizedUnbarriered(script.unsafeGet()) ||
1434             (proto && IsAboutToBeFinalizedUnbarriered(proto.unsafeGet()));
1435     }
1436 };
1437 
1438 class ObjectGroupCompartment::AllocationSiteTable
1439   : public JS::WeakCache<js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
1440                                        AllocationSiteKey, SystemAllocPolicy>>
1441 {
1442     using Table = js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
1443                                 AllocationSiteKey, SystemAllocPolicy>;
1444     using Base = JS::WeakCache<Table>;
1445 
1446   public:
1447     explicit AllocationSiteTable(Zone* zone) : Base(zone, Table()) {}
1448 };
1449 
1450 /* static */ ObjectGroup*
1451 ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode* pc,
1452                                  JSProtoKey kind, HandleObject protoArg /* = nullptr */)
1453 {
1454     MOZ_ASSERT(!useSingletonForAllocationSite(scriptArg, pc, kind));
1455     MOZ_ASSERT_IF(protoArg, kind == JSProto_Array);
1456 
1457     uint32_t offset = scriptArg->pcToOffset(pc);
1458 
1459     if (offset >= ObjectGroupCompartment::AllocationSiteKey::OFFSET_LIMIT) {
1460         if (protoArg)
1461             return defaultNewGroup(cx, GetClassForProtoKey(kind), TaggedProto(protoArg));
1462         return defaultNewGroup(cx, kind);
1463     }
1464 
1465     ObjectGroupCompartment::AllocationSiteTable*& table =
1466         cx->compartment()->objectGroups.allocationSiteTable;
1467 
1468     if (!table) {
1469         table = cx->new_<ObjectGroupCompartment::AllocationSiteTable>(cx->zone());
1470         if (!table || !table->init()) {
1471             ReportOutOfMemory(cx);
1472             js_delete(table);
1473             table = nullptr;
1474             return nullptr;
1475         }
1476     }
1477 
1478     RootedScript script(cx, scriptArg);
1479     RootedObject proto(cx, protoArg);
1480     if (!proto && kind != JSProto_Null && !GetBuiltinPrototype(cx, kind, &proto))
1481         return nullptr;
1482 
1483     Rooted<ObjectGroupCompartment::AllocationSiteKey> key(cx,
1484         ObjectGroupCompartment::AllocationSiteKey(script, offset, kind, proto));
1485 
1486     ObjectGroupCompartment::AllocationSiteTable::AddPtr p = table->lookupForAdd(key);
1487     if (p)
1488         return p->value();
1489 
1490     AutoEnterAnalysis enter(cx);
1491 
1492     Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
1493     ObjectGroup* res = ObjectGroupCompartment::makeGroup(cx, GetClassForProtoKey(kind), tagged,
1494                                                          OBJECT_FLAG_FROM_ALLOCATION_SITE);
1495     if (!res)
1496         return nullptr;
1497 
1498     if (JSOp(*pc) == JSOP_NEWOBJECT) {
1499         // Keep track of the preliminary objects with this group, so we can try
1500         // to use an unboxed layout for the object once some are allocated.
1501         Shape* shape = script->getObject(pc)->as<PlainObject>().lastProperty();
1502         if (!shape->isEmptyShape()) {
1503             PreliminaryObjectArrayWithTemplate* preliminaryObjects =
1504                 cx->new_<PreliminaryObjectArrayWithTemplate>(shape);
1505             if (preliminaryObjects)
1506                 res->setPreliminaryObjects(preliminaryObjects);
1507             else
1508                 cx->recoverFromOutOfMemory();
1509         }
1510     }
1511 
1512     if (kind == JSProto_Array &&
1513         (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) &&
1514         cx->options().unboxedArrays())
1515     {
1516         PreliminaryObjectArrayWithTemplate* preliminaryObjects =
1517             cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
1518         if (preliminaryObjects)
1519             res->setPreliminaryObjects(preliminaryObjects);
1520         else
1521             cx->recoverFromOutOfMemory();
1522     }
1523 
1524     if (!table->add(p, key, res)) {
1525         ReportOutOfMemory(cx);
1526         return nullptr;
1527     }
1528 
1529     return res;
1530 }
1531 
1532 void
1533 ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
1534                                                    JSProtoKey kind, ObjectGroup* group)
1535 {
1536     AllocationSiteKey key(script, script->pcToOffset(pc), kind, group->proto().toObjectOrNull());
1537 
1538     AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key);
1539     MOZ_RELEASE_ASSERT(p);
1540     allocationSiteTable->get().remove(p);
1541     {
1542         AutoEnterOOMUnsafeRegion oomUnsafe;
1543         if (!allocationSiteTable->putNew(key, group))
1544             oomUnsafe.crash("Inconsistent object table");
1545     }
1546 }
1547 
1548 /* static */ ObjectGroup*
1549 ObjectGroup::callingAllocationSiteGroup(JSContext* cx, JSProtoKey key, HandleObject proto)
1550 {
1551     MOZ_ASSERT_IF(proto, key == JSProto_Array);
1552 
1553     jsbytecode* pc;
1554     RootedScript script(cx, cx->currentScript(&pc));
1555     if (script)
1556         return allocationSiteGroup(cx, script, pc, key, proto);
1557     if (proto)
1558         return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto));
1559     return defaultNewGroup(cx, key);
1560 }
1561 
1562 /* static */ bool
1563 ObjectGroup::setAllocationSiteObjectGroup(JSContext* cx,
1564                                           HandleScript script, jsbytecode* pc,
1565                                           HandleObject obj, bool singleton)
1566 {
1567     JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass());
1568     MOZ_ASSERT(key != JSProto_Null);
1569     MOZ_ASSERT(singleton == useSingletonForAllocationSite(script, pc, key));
1570 
1571     if (singleton) {
1572         MOZ_ASSERT(obj->isSingleton());
1573 
1574         /*
1575          * Inference does not account for types of run-once initializer
1576          * objects, as these may not be created until after the script
1577          * has been analyzed.
1578          */
1579         TypeScript::Monitor(cx, script, pc, ObjectValue(*obj));
1580     } else {
1581         ObjectGroup* group = allocationSiteGroup(cx, script, pc, key);
1582         if (!group)
1583             return false;
1584         obj->setGroup(group);
1585     }
1586 
1587     return true;
1588 }
1589 
1590 /* static */ ArrayObject*
1591 ObjectGroup::getOrFixupCopyOnWriteObject(JSContext* cx, HandleScript script, jsbytecode* pc)
1592 {
1593     // Make sure that the template object for script/pc has a type indicating
1594     // that the object and its copies have copy on write elements.
1595     RootedArrayObject obj(cx, &script->getObject(GET_UINT32_INDEX(pc))->as<ArrayObject>());
1596     MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
1597 
1598     if (obj->group()->fromAllocationSite()) {
1599         MOZ_ASSERT(obj->group()->hasAnyFlags(OBJECT_FLAG_COPY_ON_WRITE));
1600         return obj;
1601     }
1602 
1603     RootedObjectGroup group(cx, allocationSiteGroup(cx, script, pc, JSProto_Array));
1604     if (!group)
1605         return nullptr;
1606 
1607     group->addFlags(OBJECT_FLAG_COPY_ON_WRITE);
1608 
1609     // Update type information in the initializer object group.
1610     MOZ_ASSERT(obj->slotSpan() == 0);
1611     for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
1612         const Value& v = obj->getDenseElement(i);
1613         AddTypePropertyId(cx, group, nullptr, JSID_VOID, v);
1614     }
1615 
1616     obj->setGroup(group);
1617     return obj;
1618 }
1619 
1620 /* static */ ArrayObject*
1621 ObjectGroup::getCopyOnWriteObject(JSScript* script, jsbytecode* pc)
1622 {
1623     // getOrFixupCopyOnWriteObject should already have been called for
1624     // script/pc, ensuring that the template object has a group with the
1625     // COPY_ON_WRITE flag. We don't assert this here, due to a corner case
1626     // where this property doesn't hold. See jsop_newarray_copyonwrite in
1627     // IonBuilder.
1628     ArrayObject* obj = &script->getObject(GET_UINT32_INDEX(pc))->as<ArrayObject>();
1629     MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
1630 
1631     return obj;
1632 }
1633 
1634 /* static */ bool
1635 ObjectGroup::findAllocationSite(JSContext* cx, ObjectGroup* group,
1636                                 JSScript** script, uint32_t* offset)
1637 {
1638     *script = nullptr;
1639     *offset = 0;
1640 
1641     const ObjectGroupCompartment::AllocationSiteTable* table =
1642         cx->compartment()->objectGroups.allocationSiteTable;
1643 
1644     if (!table)
1645         return false;
1646 
1647     for (ObjectGroupCompartment::AllocationSiteTable::Range r = table->all();
1648          !r.empty();
1649          r.popFront())
1650     {
1651         if (group == r.front().value()) {
1652             *script = r.front().key().script;
1653             *offset = r.front().key().offset;
1654             return true;
1655         }
1656     }
1657 
1658     return false;
1659 }
1660 
1661 /////////////////////////////////////////////////////////////////////
1662 // ObjectGroupCompartment
1663 /////////////////////////////////////////////////////////////////////
1664 
1665 ObjectGroupCompartment::ObjectGroupCompartment()
1666 {
1667     PodZero(this);
1668 }
1669 
1670 ObjectGroupCompartment::~ObjectGroupCompartment()
1671 {
1672     js_delete(defaultNewTable);
1673     js_delete(lazyTable);
1674     js_delete(arrayObjectTable);
1675     js_delete(plainObjectTable);
1676     js_delete(allocationSiteTable);
1677 }
1678 
1679 void
1680 ObjectGroupCompartment::removeDefaultNewGroup(const Class* clasp, TaggedProto proto,
1681                                               JSObject* associated)
1682 {
1683     auto p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated));
1684     MOZ_RELEASE_ASSERT(p);
1685 
1686     defaultNewTable->get().remove(p);
1687 }
1688 
1689 void
1690 ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
1691                                                JSObject* associated, ObjectGroup* group)
1692 {
1693     NewEntry::Lookup lookup(clasp, proto, associated);
1694 
1695     auto p = defaultNewTable->lookup(lookup);
1696     MOZ_RELEASE_ASSERT(p);
1697     defaultNewTable->get().remove(p);
1698     {
1699         AutoEnterOOMUnsafeRegion oomUnsafe;
1700         if (!defaultNewTable->putNew(lookup, NewEntry(group, associated)))
1701             oomUnsafe.crash("Inconsistent object table");
1702     }
1703 }
1704 
1705 /* static */
1706 ObjectGroup*
1707 ObjectGroupCompartment::makeGroup(ExclusiveContext* cx, const Class* clasp,
1708                                   Handle<TaggedProto> proto,
1709                                   ObjectGroupFlags initialFlags /* = 0 */)
1710 {
1711     MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
1712 
1713     ObjectGroup* group = Allocate<ObjectGroup>(cx);
1714     if (!group)
1715         return nullptr;
1716     new(group) ObjectGroup(clasp, proto, cx->compartment(), initialFlags);
1717 
1718     return group;
1719 }
1720 
1721 void
1722 ObjectGroupCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
1723                                                size_t* allocationSiteTables,
1724                                                size_t* arrayObjectGroupTables,
1725                                                size_t* plainObjectGroupTables,
1726                                                size_t* compartmentTables)
1727 {
1728     if (allocationSiteTable)
1729         *allocationSiteTables += allocationSiteTable->sizeOfIncludingThis(mallocSizeOf);
1730 
1731     if (arrayObjectTable)
1732         *arrayObjectGroupTables += arrayObjectTable->sizeOfIncludingThis(mallocSizeOf);
1733 
1734     if (plainObjectTable) {
1735         *plainObjectGroupTables += plainObjectTable->sizeOfIncludingThis(mallocSizeOf);
1736 
1737         for (PlainObjectTable::Enum e(*plainObjectTable);
1738              !e.empty();
1739              e.popFront())
1740         {
1741             const PlainObjectKey& key = e.front().key();
1742             const PlainObjectEntry& value = e.front().value();
1743 
1744             /* key.ids and values.types have the same length. */
1745             *plainObjectGroupTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types);
1746         }
1747     }
1748 
1749     if (defaultNewTable)
1750         *compartmentTables += defaultNewTable->sizeOfIncludingThis(mallocSizeOf);
1751 
1752     if (lazyTable)
1753         *compartmentTables += lazyTable->sizeOfIncludingThis(mallocSizeOf);
1754 }
1755 
1756 void
1757 ObjectGroupCompartment::clearTables()
1758 {
1759     if (allocationSiteTable && allocationSiteTable->initialized())
1760         allocationSiteTable->clear();
1761     if (arrayObjectTable && arrayObjectTable->initialized())
1762         arrayObjectTable->clear();
1763     if (plainObjectTable && plainObjectTable->initialized()) {
1764         for (PlainObjectTable::Enum e(*plainObjectTable); !e.empty(); e.popFront()) {
1765             const PlainObjectKey& key = e.front().key();
1766             PlainObjectEntry& entry = e.front().value();
1767             js_free(key.properties);
1768             js_free(entry.types);
1769         }
1770         plainObjectTable->clear();
1771     }
1772     if (defaultNewTable && defaultNewTable->initialized())
1773         defaultNewTable->clear();
1774     if (lazyTable && lazyTable->initialized())
1775         lazyTable->clear();
1776 }
1777 
1778 /* static */ bool
1779 ObjectGroupCompartment::PlainObjectTableSweepPolicy::needsSweep(PlainObjectKey* key,
1780                                                                 PlainObjectEntry* entry)
1781 {
1782     if (!(JS::GCPolicy<PlainObjectKey>::needsSweep(key) || entry->needsSweep(key->nproperties)))
1783         return false;
1784     js_free(key->properties);
1785     js_free(entry->types);
1786     return true;
1787 }
1788 
1789 void
1790 ObjectGroupCompartment::sweep(FreeOp* fop)
1791 {
1792     /*
1793      * Iterate through the array/object group tables and remove all entries
1794      * referencing collected data. These tables only hold weak references.
1795      */
1796 
1797     if (arrayObjectTable)
1798         arrayObjectTable->sweep();
1799     if (plainObjectTable)
1800         plainObjectTable->sweep();
1801 }
1802 
1803 void
1804 ObjectGroupCompartment::fixupNewTableAfterMovingGC(NewTable* table)
1805 {
1806     /*
1807      * Each entry's hash depends on the object's prototype and we can't tell
1808      * whether that has been moved or not in sweepNewObjectGroupTable().
1809      */
1810     if (table && table->initialized()) {
1811         for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
1812             NewEntry& entry = e.mutableFront();
1813 
1814             ObjectGroup* group = entry.group.unbarrieredGet();
1815             if (IsForwarded(group)) {
1816                 group = Forwarded(group);
1817                 entry.group.set(group);
1818             }
1819             TaggedProto proto = group->proto();
1820             if (proto.isObject() && IsForwarded(proto.toObject())) {
1821                 proto = TaggedProto(Forwarded(proto.toObject()));
1822                 // Update the group's proto here so that we are able to lookup
1823                 // entries in this table before all object pointers are updated.
1824                 group->proto() = proto;
1825             }
1826             if (entry.associated && IsForwarded(entry.associated))
1827                 entry.associated = Forwarded(entry.associated);
1828         }
1829     }
1830 }
1831 
1832 #ifdef JSGC_HASH_TABLE_CHECKS
1833 
1834 void
1835 ObjectGroupCompartment::checkNewTableAfterMovingGC(NewTable* table)
1836 {
1837     /*
1838      * Assert that nothing points into the nursery or needs to be relocated, and
1839      * that the hash table entries are discoverable.
1840      */
1841     if (!table || !table->initialized())
1842         return;
1843 
1844     for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
1845         NewEntry entry = e.front();
1846         CheckGCThingAfterMovingGC(entry.group.unbarrieredGet());
1847         TaggedProto proto = entry.group.unbarrieredGet()->proto();
1848         if (proto.isObject())
1849             CheckGCThingAfterMovingGC(proto.toObject());
1850         CheckGCThingAfterMovingGC(entry.associated);
1851 
1852         const Class* clasp = entry.group.unbarrieredGet()->clasp();
1853         if (entry.associated && entry.associated->is<JSFunction>())
1854             clasp = nullptr;
1855 
1856         NewEntry::Lookup lookup(clasp, proto, entry.associated);
1857         auto ptr = table->lookup(lookup);
1858         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
1859     }
1860 }
1861 
1862 #endif // JSGC_HASH_TABLE_CHECKS
1863