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