| |
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 /* JS symbol tables. */ |
| |
8 |
| |
9 #include "vm/Shape-inl.h" |
| |
10 |
| |
11 #include "mozilla/DebugOnly.h" |
| |
12 #include "mozilla/MathAlgorithms.h" |
| |
13 #include "mozilla/PodOperations.h" |
| |
14 |
| |
15 #include "jsatom.h" |
| |
16 #include "jscntxt.h" |
| |
17 #include "jshashutil.h" |
| |
18 #include "jsobj.h" |
| |
19 |
| |
20 #include "js/HashTable.h" |
| |
21 |
| |
22 #include "jscntxtinlines.h" |
| |
23 #include "jsobjinlines.h" |
| |
24 |
| |
25 #include "vm/ObjectImpl-inl.h" |
| |
26 #include "vm/Runtime-inl.h" |
| |
27 |
| |
28 using namespace js; |
| |
29 using namespace js::gc; |
| |
30 |
| |
31 using mozilla::CeilingLog2Size; |
| |
32 using mozilla::DebugOnly; |
| |
33 using mozilla::PodZero; |
| |
34 using mozilla::RotateLeft; |
| |
35 |
| |
36 bool |
| |
37 ShapeTable::init(ThreadSafeContext *cx, Shape *lastProp) |
| |
38 { |
| |
39 /* |
| |
40 * Either we're creating a table for a large scope that was populated |
| |
41 * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or |
| |
42 * JSOP_SETPROP; or else calloc failed at least once already. In any |
| |
43 * event, let's try to grow, overallocating to hold at least twice the |
| |
44 * current population. |
| |
45 */ |
| |
46 uint32_t sizeLog2 = CeilingLog2Size(2 * entryCount); |
| |
47 if (sizeLog2 < MIN_SIZE_LOG2) |
| |
48 sizeLog2 = MIN_SIZE_LOG2; |
| |
49 |
| |
50 /* |
| |
51 * Use rt->calloc_ for memory accounting and overpressure handling |
| |
52 * without OOM reporting. See ShapeTable::change. |
| |
53 */ |
| |
54 entries = (Shape **) cx->calloc_(sizeOfEntries(JS_BIT(sizeLog2))); |
| |
55 if (!entries) |
| |
56 return false; |
| |
57 |
| |
58 hashShift = HASH_BITS - sizeLog2; |
| |
59 for (Shape::Range<NoGC> r(lastProp); !r.empty(); r.popFront()) { |
| |
60 Shape &shape = r.front(); |
| |
61 JS_ASSERT(cx->isThreadLocal(&shape)); |
| |
62 Shape **spp = search(shape.propid(), true); |
| |
63 |
| |
64 /* |
| |
65 * Beware duplicate args and arg vs. var conflicts: the youngest shape |
| |
66 * (nearest to lastProp) must win. See bug 600067. |
| |
67 */ |
| |
68 if (!SHAPE_FETCH(spp)) |
| |
69 SHAPE_STORE_PRESERVING_COLLISION(spp, &shape); |
| |
70 } |
| |
71 return true; |
| |
72 } |
| |
73 |
| |
74 void |
| |
75 Shape::removeFromDictionary(ObjectImpl *obj) |
| |
76 { |
| |
77 JS_ASSERT(inDictionary()); |
| |
78 JS_ASSERT(obj->inDictionaryMode()); |
| |
79 JS_ASSERT(listp); |
| |
80 |
| |
81 JS_ASSERT(obj->shape_->inDictionary()); |
| |
82 JS_ASSERT(obj->shape_->listp == &obj->shape_); |
| |
83 |
| |
84 if (parent) |
| |
85 parent->listp = listp; |
| |
86 *listp = parent; |
| |
87 listp = nullptr; |
| |
88 } |
| |
89 |
| |
90 void |
| |
91 Shape::insertIntoDictionary(HeapPtrShape *dictp) |
| |
92 { |
| |
93 // Don't assert inDictionaryMode() here because we may be called from |
| |
94 // JSObject::toDictionaryMode via JSObject::newDictionaryShape. |
| |
95 JS_ASSERT(inDictionary()); |
| |
96 JS_ASSERT(!listp); |
| |
97 |
| |
98 JS_ASSERT_IF(*dictp, (*dictp)->inDictionary()); |
| |
99 JS_ASSERT_IF(*dictp, (*dictp)->listp == dictp); |
| |
100 JS_ASSERT_IF(*dictp, compartment() == (*dictp)->compartment()); |
| |
101 |
| |
102 setParent(dictp->get()); |
| |
103 if (parent) |
| |
104 parent->listp = &parent; |
| |
105 listp = (HeapPtrShape *) dictp; |
| |
106 *dictp = this; |
| |
107 } |
| |
108 |
| |
109 bool |
| |
110 Shape::makeOwnBaseShape(ThreadSafeContext *cx) |
| |
111 { |
| |
112 JS_ASSERT(!base()->isOwned()); |
| |
113 JS_ASSERT(cx->isThreadLocal(this)); |
| |
114 assertSameCompartmentDebugOnly(cx, compartment()); |
| |
115 |
| |
116 BaseShape *nbase = js_NewGCBaseShape<NoGC>(cx); |
| |
117 if (!nbase) |
| |
118 return false; |
| |
119 |
| |
120 new (nbase) BaseShape(StackBaseShape(this)); |
| |
121 nbase->setOwned(base()->toUnowned()); |
| |
122 |
| |
123 this->base_ = nbase; |
| |
124 |
| |
125 return true; |
| |
126 } |
| |
127 |
| |
128 void |
| |
129 Shape::handoffTableTo(Shape *shape) |
| |
130 { |
| |
131 JS_ASSERT(inDictionary() && shape->inDictionary()); |
| |
132 |
| |
133 if (this == shape) |
| |
134 return; |
| |
135 |
| |
136 JS_ASSERT(base()->isOwned() && !shape->base()->isOwned()); |
| |
137 |
| |
138 BaseShape *nbase = base(); |
| |
139 |
| |
140 JS_ASSERT_IF(shape->hasSlot(), nbase->slotSpan() > shape->slot()); |
| |
141 |
| |
142 this->base_ = nbase->baseUnowned(); |
| |
143 nbase->adoptUnowned(shape->base()->toUnowned()); |
| |
144 |
| |
145 shape->base_ = nbase; |
| |
146 } |
| |
147 |
| |
148 /* static */ bool |
| |
149 Shape::hashify(ThreadSafeContext *cx, Shape *shape) |
| |
150 { |
| |
151 JS_ASSERT(!shape->hasTable()); |
| |
152 |
| |
153 if (!shape->ensureOwnBaseShape(cx)) |
| |
154 return false; |
| |
155 |
| |
156 ShapeTable *table = cx->new_<ShapeTable>(shape->entryCount()); |
| |
157 if (!table) |
| |
158 return false; |
| |
159 |
| |
160 if (!table->init(cx, shape)) { |
| |
161 js_free(table); |
| |
162 return false; |
| |
163 } |
| |
164 |
| |
165 shape->base()->setTable(table); |
| |
166 return true; |
| |
167 } |
| |
168 |
| |
169 /* |
| |
170 * Double hashing needs the second hash code to be relatively prime to table |
| |
171 * size, so we simply make hash2 odd. |
| |
172 */ |
| |
173 #define HASH1(hash0,shift) ((hash0) >> (shift)) |
| |
174 #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) |
| |
175 |
| |
176 Shape ** |
| |
177 ShapeTable::search(jsid id, bool adding) |
| |
178 { |
| |
179 js::HashNumber hash0, hash1, hash2; |
| |
180 int sizeLog2; |
| |
181 Shape *stored, *shape, **spp, **firstRemoved; |
| |
182 uint32_t sizeMask; |
| |
183 |
| |
184 JS_ASSERT(entries); |
| |
185 JS_ASSERT(!JSID_IS_EMPTY(id)); |
| |
186 |
| |
187 /* Compute the primary hash address. */ |
| |
188 hash0 = HashId(id); |
| |
189 hash1 = HASH1(hash0, hashShift); |
| |
190 spp = entries + hash1; |
| |
191 |
| |
192 /* Miss: return space for a new entry. */ |
| |
193 stored = *spp; |
| |
194 if (SHAPE_IS_FREE(stored)) |
| |
195 return spp; |
| |
196 |
| |
197 /* Hit: return entry. */ |
| |
198 shape = SHAPE_CLEAR_COLLISION(stored); |
| |
199 if (shape && shape->propidRaw() == id) |
| |
200 return spp; |
| |
201 |
| |
202 /* Collision: double hash. */ |
| |
203 sizeLog2 = HASH_BITS - hashShift; |
| |
204 hash2 = HASH2(hash0, sizeLog2, hashShift); |
| |
205 sizeMask = JS_BITMASK(sizeLog2); |
| |
206 |
| |
207 #ifdef DEBUG |
| |
208 uintptr_t collision_flag = SHAPE_COLLISION; |
| |
209 #endif |
| |
210 |
| |
211 /* Save the first removed entry pointer so we can recycle it if adding. */ |
| |
212 if (SHAPE_IS_REMOVED(stored)) { |
| |
213 firstRemoved = spp; |
| |
214 } else { |
| |
215 firstRemoved = nullptr; |
| |
216 if (adding && !SHAPE_HAD_COLLISION(stored)) |
| |
217 SHAPE_FLAG_COLLISION(spp, shape); |
| |
218 #ifdef DEBUG |
| |
219 collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION; |
| |
220 #endif |
| |
221 } |
| |
222 |
| |
223 for (;;) { |
| |
224 hash1 -= hash2; |
| |
225 hash1 &= sizeMask; |
| |
226 spp = entries + hash1; |
| |
227 |
| |
228 stored = *spp; |
| |
229 if (SHAPE_IS_FREE(stored)) |
| |
230 return (adding && firstRemoved) ? firstRemoved : spp; |
| |
231 |
| |
232 shape = SHAPE_CLEAR_COLLISION(stored); |
| |
233 if (shape && shape->propidRaw() == id) { |
| |
234 JS_ASSERT(collision_flag); |
| |
235 return spp; |
| |
236 } |
| |
237 |
| |
238 if (SHAPE_IS_REMOVED(stored)) { |
| |
239 if (!firstRemoved) |
| |
240 firstRemoved = spp; |
| |
241 } else { |
| |
242 if (adding && !SHAPE_HAD_COLLISION(stored)) |
| |
243 SHAPE_FLAG_COLLISION(spp, shape); |
| |
244 #ifdef DEBUG |
| |
245 collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION; |
| |
246 #endif |
| |
247 } |
| |
248 } |
| |
249 |
| |
250 /* NOTREACHED */ |
| |
251 return nullptr; |
| |
252 } |
| |
253 |
| |
254 bool |
| |
255 ShapeTable::change(int log2Delta, ThreadSafeContext *cx) |
| |
256 { |
| |
257 JS_ASSERT(entries); |
| |
258 |
| |
259 /* |
| |
260 * Grow, shrink, or compress by changing this->entries. |
| |
261 */ |
| |
262 int oldlog2 = HASH_BITS - hashShift; |
| |
263 int newlog2 = oldlog2 + log2Delta; |
| |
264 uint32_t oldsize = JS_BIT(oldlog2); |
| |
265 uint32_t newsize = JS_BIT(newlog2); |
| |
266 Shape **newTable = (Shape **) cx->calloc_(sizeOfEntries(newsize)); |
| |
267 if (!newTable) |
| |
268 return false; |
| |
269 |
| |
270 /* Now that we have newTable allocated, update members. */ |
| |
271 hashShift = HASH_BITS - newlog2; |
| |
272 removedCount = 0; |
| |
273 Shape **oldTable = entries; |
| |
274 entries = newTable; |
| |
275 |
| |
276 /* Copy only live entries, leaving removed and free ones behind. */ |
| |
277 for (Shape **oldspp = oldTable; oldsize != 0; oldspp++) { |
| |
278 Shape *shape = SHAPE_FETCH(oldspp); |
| |
279 JS_ASSERT(cx->isThreadLocal(shape)); |
| |
280 if (shape) { |
| |
281 Shape **spp = search(shape->propid(), true); |
| |
282 JS_ASSERT(SHAPE_IS_FREE(*spp)); |
| |
283 *spp = shape; |
| |
284 } |
| |
285 oldsize--; |
| |
286 } |
| |
287 |
| |
288 /* Finally, free the old entries storage. */ |
| |
289 js_free(oldTable); |
| |
290 return true; |
| |
291 } |
| |
292 |
| |
293 bool |
| |
294 ShapeTable::grow(ThreadSafeContext *cx) |
| |
295 { |
| |
296 JS_ASSERT(needsToGrow()); |
| |
297 |
| |
298 uint32_t size = capacity(); |
| |
299 int delta = removedCount < size >> 2; |
| |
300 |
| |
301 if (!change(delta, cx) && entryCount + removedCount == size - 1) { |
| |
302 js_ReportOutOfMemory(cx); |
| |
303 return false; |
| |
304 } |
| |
305 return true; |
| |
306 } |
| |
307 |
| |
308 /* static */ Shape * |
| |
309 Shape::replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base, |
| |
310 TaggedProto proto, HandleShape shape) |
| |
311 { |
| |
312 JS_ASSERT(!shape->inDictionary()); |
| |
313 |
| |
314 if (!shape->parent) { |
| |
315 /* Treat as resetting the initial property of the shape hierarchy. */ |
| |
316 AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| |
317 return EmptyShape::getInitialShape(cx, base.clasp, proto, |
| |
318 base.parent, base.metadata, kind, |
| |
319 base.flags & BaseShape::OBJECT_FLAG_MASK); |
| |
320 } |
| |
321 |
| |
322 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); |
| |
323 if (!nbase) |
| |
324 return nullptr; |
| |
325 |
| |
326 StackShape child(shape); |
| |
327 child.base = nbase; |
| |
328 |
| |
329 return cx->compartment()->propertyTree.getChild(cx, shape->parent, child); |
| |
330 } |
| |
331 |
| |
332 /* |
| |
333 * Get or create a property-tree or dictionary child property of |parent|, |
| |
334 * which must be lastProperty() if inDictionaryMode(), else parent must be |
| |
335 * one of lastProperty() or lastProperty()->parent. |
| |
336 */ |
| |
337 /* static */ Shape * |
| |
338 JSObject::getChildPropertyOnDictionary(ThreadSafeContext *cx, JS::HandleObject obj, |
| |
339 HandleShape parent, js::StackShape &child) |
| |
340 { |
| |
341 /* |
| |
342 * Shared properties have no slot, but slot_ will reflect that of parent. |
| |
343 * Unshared properties allocate a slot here but may lose it due to a |
| |
344 * JS_ClearScope call. |
| |
345 */ |
| |
346 if (!child.hasSlot()) { |
| |
347 child.setSlot(parent->maybeSlot()); |
| |
348 } else { |
| |
349 if (child.hasMissingSlot()) { |
| |
350 uint32_t slot; |
| |
351 if (!allocSlot(cx, obj, &slot)) |
| |
352 return nullptr; |
| |
353 child.setSlot(slot); |
| |
354 } else { |
| |
355 /* |
| |
356 * Slots can only be allocated out of order on objects in |
| |
357 * dictionary mode. Otherwise the child's slot must be after the |
| |
358 * parent's slot (if it has one), because slot number determines |
| |
359 * slot span for objects with that shape. Usually child slot |
| |
360 * *immediately* follows parent slot, but there may be a slot gap |
| |
361 * when the object uses some -- but not all -- of its reserved |
| |
362 * slots to store properties. |
| |
363 */ |
| |
364 JS_ASSERT(obj->inDictionaryMode() || |
| |
365 parent->hasMissingSlot() || |
| |
366 child.slot() == parent->maybeSlot() + 1 || |
| |
367 (parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) && |
| |
368 child.slot() == JSSLOT_FREE(obj->getClass()))); |
| |
369 } |
| |
370 } |
| |
371 |
| |
372 RootedShape shape(cx); |
| |
373 |
| |
374 if (obj->inDictionaryMode()) { |
| |
375 JS_ASSERT(parent == obj->lastProperty()); |
| |
376 RootedGeneric<StackShape*> childRoot(cx, &child); |
| |
377 shape = js_NewGCShape(cx); |
| |
378 if (!shape) |
| |
379 return nullptr; |
| |
380 if (childRoot->hasSlot() && childRoot->slot() >= obj->lastProperty()->base()->slotSpan()) { |
| |
381 if (!JSObject::setSlotSpan(cx, obj, childRoot->slot() + 1)) |
| |
382 return nullptr; |
| |
383 } |
| |
384 shape->initDictionaryShape(*childRoot, obj->numFixedSlots(), &obj->shape_); |
| |
385 } |
| |
386 |
| |
387 return shape; |
| |
388 } |
| |
389 |
| |
390 /* static */ Shape * |
| |
391 JSObject::getChildProperty(ExclusiveContext *cx, |
| |
392 HandleObject obj, HandleShape parent, StackShape &unrootedChild) |
| |
393 { |
| |
394 RootedGeneric<StackShape*> child(cx, &unrootedChild); |
| |
395 RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child)); |
| |
396 |
| |
397 if (!obj->inDictionaryMode()) { |
| |
398 shape = cx->compartment()->propertyTree.getChild(cx, parent, *child); |
| |
399 if (!shape) |
| |
400 return nullptr; |
| |
401 //JS_ASSERT(shape->parent == parent); |
| |
402 //JS_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent); |
| |
403 if (!JSObject::setLastProperty(cx, obj, shape)) |
| |
404 return nullptr; |
| |
405 } |
| |
406 |
| |
407 return shape; |
| |
408 } |
| |
409 |
| |
410 /* static */ Shape * |
| |
411 JSObject::lookupChildProperty(ThreadSafeContext *cx, |
| |
412 HandleObject obj, HandleShape parent, StackShape &unrootedChild) |
| |
413 { |
| |
414 RootedGeneric<StackShape*> child(cx, &unrootedChild); |
| |
415 JS_ASSERT(cx->isThreadLocal(obj)); |
| |
416 |
| |
417 RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child)); |
| |
418 |
| |
419 if (!obj->inDictionaryMode()) { |
| |
420 shape = cx->compartment_->propertyTree.lookupChild(cx, parent, *child); |
| |
421 if (!shape) |
| |
422 return nullptr; |
| |
423 if (!JSObject::setLastProperty(cx, obj, shape)) |
| |
424 return nullptr; |
| |
425 } |
| |
426 |
| |
427 return shape; |
| |
428 } |
| |
429 |
| |
430 bool |
| |
431 js::ObjectImpl::toDictionaryMode(ThreadSafeContext *cx) |
| |
432 { |
| |
433 JS_ASSERT(!inDictionaryMode()); |
| |
434 |
| |
435 /* We allocate the shapes from cx->compartment(), so make sure it's right. */ |
| |
436 JS_ASSERT(cx->isInsideCurrentCompartment(this)); |
| |
437 |
| |
438 /* |
| |
439 * This function is thread safe as long as the object is thread local. It |
| |
440 * does not modify the shared shapes, and only allocates newly allocated |
| |
441 * (and thus also thread local) shapes. |
| |
442 */ |
| |
443 JS_ASSERT(cx->isThreadLocal(this)); |
| |
444 |
| |
445 uint32_t span = slotSpan(); |
| |
446 |
| |
447 Rooted<ObjectImpl*> self(cx, this); |
| |
448 |
| |
449 /* |
| |
450 * Clone the shapes into a new dictionary list. Don't update the |
| |
451 * last property of this object until done, otherwise a GC |
| |
452 * triggered while creating the dictionary will get the wrong |
| |
453 * slot span for this object. |
| |
454 */ |
| |
455 RootedShape root(cx); |
| |
456 RootedShape dictionaryShape(cx); |
| |
457 |
| |
458 RootedShape shape(cx, lastProperty()); |
| |
459 while (shape) { |
| |
460 JS_ASSERT(!shape->inDictionary()); |
| |
461 |
| |
462 Shape *dprop = js_NewGCShape(cx); |
| |
463 if (!dprop) { |
| |
464 js_ReportOutOfMemory(cx); |
| |
465 return false; |
| |
466 } |
| |
467 |
| |
468 HeapPtrShape *listp = dictionaryShape |
| |
469 ? &dictionaryShape->parent |
| |
470 : (HeapPtrShape *) root.address(); |
| |
471 |
| |
472 StackShape child(shape); |
| |
473 dprop->initDictionaryShape(child, self->numFixedSlots(), listp); |
| |
474 |
| |
475 JS_ASSERT(!dprop->hasTable()); |
| |
476 dictionaryShape = dprop; |
| |
477 shape = shape->previous(); |
| |
478 } |
| |
479 |
| |
480 if (!Shape::hashify(cx, root)) { |
| |
481 js_ReportOutOfMemory(cx); |
| |
482 return false; |
| |
483 } |
| |
484 |
| |
485 JS_ASSERT((Shape **) root->listp == root.address()); |
| |
486 root->listp = &self->shape_; |
| |
487 self->shape_ = root; |
| |
488 |
| |
489 JS_ASSERT(self->inDictionaryMode()); |
| |
490 root->base()->setSlotSpan(span); |
| |
491 |
| |
492 return true; |
| |
493 } |
| |
494 |
| |
495 /* |
| |
496 * Normalize stub getter and setter values for faster is-stub testing in the |
| |
497 * SHAPE_CALL_[GS]ETTER macros. |
| |
498 */ |
| |
499 static inline bool |
| |
500 NormalizeGetterAndSetter(JSObject *obj, |
| |
501 jsid id, unsigned attrs, unsigned flags, |
| |
502 PropertyOp &getter, |
| |
503 StrictPropertyOp &setter) |
| |
504 { |
| |
505 if (setter == JS_StrictPropertyStub) { |
| |
506 JS_ASSERT(!(attrs & JSPROP_SETTER)); |
| |
507 setter = nullptr; |
| |
508 } |
| |
509 if (getter == JS_PropertyStub) { |
| |
510 JS_ASSERT(!(attrs & JSPROP_GETTER)); |
| |
511 getter = nullptr; |
| |
512 } |
| |
513 |
| |
514 return true; |
| |
515 } |
| |
516 |
| |
517 /* static */ Shape * |
| |
518 JSObject::addProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, |
| |
519 PropertyOp getter, StrictPropertyOp setter, |
| |
520 uint32_t slot, unsigned attrs, |
| |
521 unsigned flags, bool allowDictionary) |
| |
522 { |
| |
523 JS_ASSERT(!JSID_IS_VOID(id)); |
| |
524 |
| |
525 bool extensible; |
| |
526 if (!JSObject::isExtensible(cx, obj, &extensible)) |
| |
527 return nullptr; |
| |
528 if (!extensible) { |
| |
529 if (cx->isJSContext()) |
| |
530 obj->reportNotExtensible(cx->asJSContext()); |
| |
531 return nullptr; |
| |
532 } |
| |
533 |
| |
534 NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter); |
| |
535 |
| |
536 Shape **spp = nullptr; |
| |
537 if (obj->inDictionaryMode()) |
| |
538 spp = obj->lastProperty()->table().search(id, true); |
| |
539 |
| |
540 return addPropertyInternal<SequentialExecution>(cx, obj, id, getter, setter, slot, attrs, |
| |
541 flags, spp, allowDictionary); |
| |
542 } |
| |
543 |
| |
544 static bool |
| |
545 ShouldConvertToDictionary(JSObject *obj) |
| |
546 { |
| |
547 /* |
| |
548 * Use a lower limit if this object is likely a hashmap (SETELEM was used |
| |
549 * to set properties). |
| |
550 */ |
| |
551 if (obj->hadElementsAccess()) |
| |
552 return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS; |
| |
553 return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT; |
| |
554 } |
| |
555 |
| |
556 template <ExecutionMode mode> |
| |
557 static inline UnownedBaseShape * |
| |
558 GetOrLookupUnownedBaseShape(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, |
| |
559 StackBaseShape &base) |
| |
560 { |
| |
561 if (mode == ParallelExecution) |
| |
562 return BaseShape::lookupUnowned(cx, base); |
| |
563 return BaseShape::getUnowned(cx->asExclusiveContext(), base); |
| |
564 } |
| |
565 |
| |
566 template <ExecutionMode mode> |
| |
567 /* static */ Shape * |
| |
568 JSObject::addPropertyInternal(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, |
| |
569 HandleObject obj, HandleId id, |
| |
570 PropertyOp getter, StrictPropertyOp setter, |
| |
571 uint32_t slot, unsigned attrs, |
| |
572 unsigned flags, Shape **spp, |
| |
573 bool allowDictionary) |
| |
574 { |
| |
575 JS_ASSERT(cx->isThreadLocal(obj)); |
| |
576 JS_ASSERT_IF(!allowDictionary, !obj->inDictionaryMode()); |
| |
577 |
| |
578 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
| |
579 |
| |
580 /* |
| |
581 * The code below deals with either converting obj to dictionary mode or |
| |
582 * growing an object that's already in dictionary mode. Either way, |
| |
583 * dictionray operations are safe if thread local. |
| |
584 */ |
| |
585 ShapeTable *table = nullptr; |
| |
586 if (!obj->inDictionaryMode()) { |
| |
587 bool stableSlot = |
| |
588 (slot == SHAPE_INVALID_SLOT) || |
| |
589 obj->lastProperty()->hasMissingSlot() || |
| |
590 (slot == obj->lastProperty()->maybeSlot() + 1); |
| |
591 JS_ASSERT_IF(!allowDictionary, stableSlot); |
| |
592 if (allowDictionary && |
| |
593 (!stableSlot || ShouldConvertToDictionary(obj))) |
| |
594 { |
| |
595 if (!obj->toDictionaryMode(cx)) |
| |
596 return nullptr; |
| |
597 table = &obj->lastProperty()->table(); |
| |
598 spp = table->search(id, true); |
| |
599 } |
| |
600 } else { |
| |
601 table = &obj->lastProperty()->table(); |
| |
602 if (table->needsToGrow()) { |
| |
603 if (!table->grow(cx)) |
| |
604 return nullptr; |
| |
605 spp = table->search(id, true); |
| |
606 JS_ASSERT(!SHAPE_FETCH(spp)); |
| |
607 } |
| |
608 } |
| |
609 |
| |
610 JS_ASSERT(!!table == !!spp); |
| |
611 |
| |
612 /* Find or create a property tree node labeled by our arguments. */ |
| |
613 RootedShape shape(cx); |
| |
614 { |
| |
615 RootedShape last(cx, obj->lastProperty()); |
| |
616 |
| |
617 uint32_t index; |
| |
618 bool indexed = js_IdIsIndex(id, &index); |
| |
619 |
| |
620 Rooted<UnownedBaseShape*> nbase(cx); |
| |
621 if (last->base()->matchesGetterSetter(getter, setter) && !indexed) { |
| |
622 nbase = last->base()->unowned(); |
| |
623 } else { |
| |
624 StackBaseShape base(last->base()); |
| |
625 base.updateGetterSetter(attrs, getter, setter); |
| |
626 if (indexed) |
| |
627 base.flags |= BaseShape::INDEXED; |
| |
628 nbase = GetOrLookupUnownedBaseShape<mode>(cx, base); |
| |
629 if (!nbase) |
| |
630 return nullptr; |
| |
631 } |
| |
632 |
| |
633 StackShape child(nbase, id, slot, attrs, flags); |
| |
634 shape = getOrLookupChildProperty<mode>(cx, obj, last, child); |
| |
635 } |
| |
636 |
| |
637 if (shape) { |
| |
638 JS_ASSERT(shape == obj->lastProperty()); |
| |
639 |
| |
640 if (table) { |
| |
641 /* Store the tree node pointer in the table entry for id. */ |
| |
642 SHAPE_STORE_PRESERVING_COLLISION(spp, static_cast<Shape *>(shape)); |
| |
643 ++table->entryCount; |
| |
644 |
| |
645 /* Pass the table along to the new last property, namely shape. */ |
| |
646 JS_ASSERT(&shape->parent->table() == table); |
| |
647 shape->parent->handoffTableTo(shape); |
| |
648 } |
| |
649 |
| |
650 obj->checkShapeConsistency(); |
| |
651 return shape; |
| |
652 } |
| |
653 |
| |
654 obj->checkShapeConsistency(); |
| |
655 return nullptr; |
| |
656 } |
| |
657 |
| |
658 template /* static */ Shape * |
| |
659 JSObject::addPropertyInternal<SequentialExecution>(ExclusiveContext *cx, |
| |
660 HandleObject obj, HandleId id, |
| |
661 PropertyOp getter, StrictPropertyOp setter, |
| |
662 uint32_t slot, unsigned attrs, |
| |
663 unsigned flags, Shape **spp, |
| |
664 bool allowDictionary); |
| |
665 template /* static */ Shape * |
| |
666 JSObject::addPropertyInternal<ParallelExecution>(ForkJoinContext *cx, |
| |
667 HandleObject obj, HandleId id, |
| |
668 PropertyOp getter, StrictPropertyOp setter, |
| |
669 uint32_t slot, unsigned attrs, |
| |
670 unsigned flags, Shape **spp, |
| |
671 bool allowDictionary); |
| |
672 |
| |
673 JSObject * |
| |
674 js::NewReshapedObject(JSContext *cx, HandleTypeObject type, JSObject *parent, |
| |
675 gc::AllocKind allocKind, HandleShape shape, NewObjectKind newKind) |
| |
676 { |
| |
677 RootedObject res(cx, NewObjectWithType(cx, type, parent, allocKind, newKind)); |
| |
678 if (!res) |
| |
679 return nullptr; |
| |
680 |
| |
681 if (shape->isEmptyShape()) |
| |
682 return res; |
| |
683 |
| |
684 /* Get all the ids in the object, in order. */ |
| |
685 js::AutoIdVector ids(cx); |
| |
686 { |
| |
687 for (unsigned i = 0; i <= shape->slot(); i++) { |
| |
688 if (!ids.append(JSID_VOID)) |
| |
689 return nullptr; |
| |
690 } |
| |
691 Shape *nshape = shape; |
| |
692 while (!nshape->isEmptyShape()) { |
| |
693 ids[nshape->slot()] = nshape->propid(); |
| |
694 nshape = nshape->previous(); |
| |
695 } |
| |
696 } |
| |
697 |
| |
698 /* Construct the new shape, without updating type information. */ |
| |
699 RootedId id(cx); |
| |
700 RootedShape newShape(cx, res->lastProperty()); |
| |
701 for (unsigned i = 0; i < ids.length(); i++) { |
| |
702 id = ids[i]; |
| |
703 JS_ASSERT(!res->nativeContains(cx, id)); |
| |
704 |
| |
705 uint32_t index; |
| |
706 bool indexed = js_IdIsIndex(id, &index); |
| |
707 |
| |
708 Rooted<UnownedBaseShape*> nbase(cx, newShape->base()->unowned()); |
| |
709 if (indexed) { |
| |
710 StackBaseShape base(nbase); |
| |
711 base.flags |= BaseShape::INDEXED; |
| |
712 nbase = GetOrLookupUnownedBaseShape<SequentialExecution>(cx, base); |
| |
713 if (!nbase) |
| |
714 return nullptr; |
| |
715 } |
| |
716 |
| |
717 StackShape child(nbase, id, i, JSPROP_ENUMERATE, 0); |
| |
718 newShape = cx->compartment()->propertyTree.getChild(cx, newShape, child); |
| |
719 if (!newShape) |
| |
720 return nullptr; |
| |
721 if (!JSObject::setLastProperty(cx, res, newShape)) |
| |
722 return nullptr; |
| |
723 } |
| |
724 |
| |
725 return res; |
| |
726 } |
| |
727 |
| |
728 /* |
| |
729 * Check and adjust the new attributes for the shape to make sure that our |
| |
730 * slot access optimizations are sound. It is responsibility of the callers to |
| |
731 * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]]. |
| |
732 */ |
| |
733 static inline bool |
| |
734 CheckCanChangeAttrs(ThreadSafeContext *cx, JSObject *obj, Shape *shape, unsigned *attrsp) |
| |
735 { |
| |
736 if (shape->configurable()) |
| |
737 return true; |
| |
738 |
| |
739 /* A permanent property must stay permanent. */ |
| |
740 *attrsp |= JSPROP_PERMANENT; |
| |
741 |
| |
742 /* Reject attempts to remove a slot from the permanent data property. */ |
| |
743 if (shape->isDataDescriptor() && shape->hasSlot() && |
| |
744 (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))) |
| |
745 { |
| |
746 if (cx->isJSContext()) |
| |
747 obj->reportNotConfigurable(cx->asJSContext(), shape->propid()); |
| |
748 return false; |
| |
749 } |
| |
750 |
| |
751 return true; |
| |
752 } |
| |
753 |
| |
754 template <ExecutionMode mode> |
| |
755 /* static */ Shape * |
| |
756 JSObject::putProperty(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, |
| |
757 HandleObject obj, HandleId id, |
| |
758 PropertyOp getter, StrictPropertyOp setter, |
| |
759 uint32_t slot, unsigned attrs, unsigned flags) |
| |
760 { |
| |
761 JS_ASSERT(cx->isThreadLocal(obj)); |
| |
762 JS_ASSERT(!JSID_IS_VOID(id)); |
| |
763 |
| |
764 #ifdef DEBUG |
| |
765 if (obj->is<ArrayObject>()) { |
| |
766 ArrayObject *arr = &obj->as<ArrayObject>(); |
| |
767 uint32_t index; |
| |
768 if (js_IdIsIndex(id, &index)) |
| |
769 JS_ASSERT(index < arr->length() || arr->lengthIsWritable()); |
| |
770 } |
| |
771 #endif |
| |
772 |
| |
773 NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter); |
| |
774 |
| |
775 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
| |
776 |
| |
777 /* |
| |
778 * Search for id in order to claim its entry if table has been allocated. |
| |
779 * |
| |
780 * Note that we can only try to claim an entry in a table that is thread |
| |
781 * local. An object may be thread local *without* its shape being thread |
| |
782 * local. The only thread local objects that *also* have thread local |
| |
783 * shapes are dictionaries that were allocated/converted thread |
| |
784 * locally. Only for those objects we can try to claim an entry in its |
| |
785 * shape table. |
| |
786 */ |
| |
787 Shape **spp; |
| |
788 RootedShape shape(cx, (mode == ParallelExecution |
| |
789 ? Shape::searchThreadLocal(cx, obj->lastProperty(), id, &spp, |
| |
790 cx->isThreadLocal(obj->lastProperty())) |
| |
791 : Shape::search(cx->asExclusiveContext(), obj->lastProperty(), id, |
| |
792 &spp, true))); |
| |
793 if (!shape) { |
| |
794 /* |
| |
795 * You can't add properties to a non-extensible object, but you can change |
| |
796 * attributes of properties in such objects. |
| |
797 */ |
| |
798 bool extensible; |
| |
799 |
| |
800 if (mode == ParallelExecution) { |
| |
801 if (obj->is<ProxyObject>()) |
| |
802 return nullptr; |
| |
803 extensible = obj->nonProxyIsExtensible(); |
| |
804 } else { |
| |
805 if (!JSObject::isExtensible(cx->asExclusiveContext(), obj, &extensible)) |
| |
806 return nullptr; |
| |
807 } |
| |
808 |
| |
809 if (!extensible) { |
| |
810 if (cx->isJSContext()) |
| |
811 obj->reportNotExtensible(cx->asJSContext()); |
| |
812 return nullptr; |
| |
813 } |
| |
814 |
| |
815 return addPropertyInternal<mode>(cx, obj, id, getter, setter, slot, attrs, flags, |
| |
816 spp, true); |
| |
817 } |
| |
818 |
| |
819 /* Property exists: search must have returned a valid *spp. */ |
| |
820 JS_ASSERT_IF(spp, !SHAPE_IS_REMOVED(*spp)); |
| |
821 |
| |
822 if (!CheckCanChangeAttrs(cx, obj, shape, &attrs)) |
| |
823 return nullptr; |
| |
824 |
| |
825 /* |
| |
826 * If the caller wants to allocate a slot, but doesn't care which slot, |
| |
827 * copy the existing shape's slot into slot so we can match shape, if all |
| |
828 * other members match. |
| |
829 */ |
| |
830 bool hadSlot = shape->hasSlot(); |
| |
831 uint32_t oldSlot = shape->maybeSlot(); |
| |
832 if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot) |
| |
833 slot = oldSlot; |
| |
834 |
| |
835 Rooted<UnownedBaseShape*> nbase(cx); |
| |
836 { |
| |
837 uint32_t index; |
| |
838 bool indexed = js_IdIsIndex(id, &index); |
| |
839 StackBaseShape base(obj->lastProperty()->base()); |
| |
840 base.updateGetterSetter(attrs, getter, setter); |
| |
841 if (indexed) |
| |
842 base.flags |= BaseShape::INDEXED; |
| |
843 nbase = GetOrLookupUnownedBaseShape<mode>(cx, base); |
| |
844 if (!nbase) |
| |
845 return nullptr; |
| |
846 } |
| |
847 |
| |
848 /* |
| |
849 * Now that we've possibly preserved slot, check whether all members match. |
| |
850 * If so, this is a redundant "put" and we can return without more work. |
| |
851 */ |
| |
852 if (shape->matchesParamsAfterId(nbase, slot, attrs, flags)) |
| |
853 return shape; |
| |
854 |
| |
855 /* |
| |
856 * Overwriting a non-last property requires switching to dictionary mode. |
| |
857 * The shape tree is shared immutable, and we can't removeProperty and then |
| |
858 * addPropertyInternal because a failure under add would lose data. |
| |
859 */ |
| |
860 if (shape != obj->lastProperty() && !obj->inDictionaryMode()) { |
| |
861 if (!obj->toDictionaryMode(cx)) |
| |
862 return nullptr; |
| |
863 spp = obj->lastProperty()->table().search(shape->propid(), false); |
| |
864 shape = SHAPE_FETCH(spp); |
| |
865 } |
| |
866 |
| |
867 JS_ASSERT_IF(shape->hasSlot() && !(attrs & JSPROP_SHARED), shape->slot() == slot); |
| |
868 |
| |
869 if (obj->inDictionaryMode()) { |
| |
870 /* |
| |
871 * Updating some property in a dictionary-mode object. Create a new |
| |
872 * shape for the existing property, and also generate a new shape for |
| |
873 * the last property of the dictionary (unless the modified property |
| |
874 * is also the last property). |
| |
875 */ |
| |
876 bool updateLast = (shape == obj->lastProperty()); |
| |
877 shape = obj->replaceWithNewEquivalentShape(cx, shape); |
| |
878 if (!shape) |
| |
879 return nullptr; |
| |
880 if (!updateLast && !obj->generateOwnShape(cx)) |
| |
881 return nullptr; |
| |
882 |
| |
883 /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */ |
| |
884 if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED)) { |
| |
885 if (!allocSlot(cx, obj, &slot)) |
| |
886 return nullptr; |
| |
887 } |
| |
888 |
| |
889 if (updateLast) |
| |
890 shape->base()->adoptUnowned(nbase); |
| |
891 else |
| |
892 shape->base_ = nbase; |
| |
893 |
| |
894 JS_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); |
| |
895 |
| |
896 shape->setSlot(slot); |
| |
897 shape->attrs = uint8_t(attrs); |
| |
898 shape->flags = flags | Shape::IN_DICTIONARY; |
| |
899 } else { |
| |
900 /* |
| |
901 * Updating the last property in a non-dictionary-mode object. Find an |
| |
902 * alternate shared child of the last property's previous shape. |
| |
903 */ |
| |
904 StackBaseShape base(obj->lastProperty()->base()); |
| |
905 base.updateGetterSetter(attrs, getter, setter); |
| |
906 |
| |
907 UnownedBaseShape *nbase = GetOrLookupUnownedBaseShape<mode>(cx, base); |
| |
908 if (!nbase) |
| |
909 return nullptr; |
| |
910 |
| |
911 JS_ASSERT(shape == obj->lastProperty()); |
| |
912 |
| |
913 /* Find or create a property tree node labeled by our arguments. */ |
| |
914 StackShape child(nbase, id, slot, attrs, flags); |
| |
915 RootedShape parent(cx, shape->parent); |
| |
916 Shape *newShape = getOrLookupChildProperty<mode>(cx, obj, parent, child); |
| |
917 |
| |
918 if (!newShape) { |
| |
919 obj->checkShapeConsistency(); |
| |
920 return nullptr; |
| |
921 } |
| |
922 |
| |
923 shape = newShape; |
| |
924 } |
| |
925 |
| |
926 /* |
| |
927 * Can't fail now, so free the previous incarnation's slot if the new shape |
| |
928 * has no slot. But we do not need to free oldSlot (and must not, as trying |
| |
929 * to will botch an assertion in JSObject::freeSlot) if the new last |
| |
930 * property (shape here) has a slotSpan that does not cover it. |
| |
931 */ |
| |
932 if (hadSlot && !shape->hasSlot()) { |
| |
933 if (oldSlot < obj->slotSpan()) |
| |
934 obj->freeSlot(oldSlot); |
| |
935 /* Note: The optimization based on propertyRemovals is only relevant to the main thread. */ |
| |
936 if (cx->isJSContext()) |
| |
937 ++cx->asJSContext()->runtime()->propertyRemovals; |
| |
938 } |
| |
939 |
| |
940 obj->checkShapeConsistency(); |
| |
941 |
| |
942 return shape; |
| |
943 } |
| |
944 |
| |
945 template /* static */ Shape * |
| |
946 JSObject::putProperty<SequentialExecution>(ExclusiveContext *cx, |
| |
947 HandleObject obj, HandleId id, |
| |
948 PropertyOp getter, StrictPropertyOp setter, |
| |
949 uint32_t slot, unsigned attrs, |
| |
950 unsigned flags); |
| |
951 template /* static */ Shape * |
| |
952 JSObject::putProperty<ParallelExecution>(ForkJoinContext *cx, |
| |
953 HandleObject obj, HandleId id, |
| |
954 PropertyOp getter, StrictPropertyOp setter, |
| |
955 uint32_t slot, unsigned attrs, |
| |
956 unsigned flags); |
| |
957 |
| |
958 template <ExecutionMode mode> |
| |
959 /* static */ Shape * |
| |
960 JSObject::changeProperty(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, |
| |
961 HandleObject obj, HandleShape shape, unsigned attrs, |
| |
962 unsigned mask, PropertyOp getter, StrictPropertyOp setter) |
| |
963 { |
| |
964 JS_ASSERT(cx->isThreadLocal(obj)); |
| |
965 JS_ASSERT(obj->nativeContainsPure(shape)); |
| |
966 |
| |
967 attrs |= shape->attrs & mask; |
| |
968 JS_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); |
| |
969 |
| |
970 /* Allow only shared (slotless) => unshared (slotful) transition. */ |
| |
971 JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) || |
| |
972 !(attrs & JSPROP_SHARED)); |
| |
973 |
| |
974 if (mode == ParallelExecution) { |
| |
975 if (!types::IsTypePropertyIdMarkedNonData(obj, shape->propid())) |
| |
976 return nullptr; |
| |
977 } else { |
| |
978 types::MarkTypePropertyNonData(cx->asExclusiveContext(), obj, shape->propid()); |
| |
979 } |
| |
980 |
| |
981 if (getter == JS_PropertyStub) |
| |
982 getter = nullptr; |
| |
983 if (setter == JS_StrictPropertyStub) |
| |
984 setter = nullptr; |
| |
985 |
| |
986 if (!CheckCanChangeAttrs(cx, obj, shape, &attrs)) |
| |
987 return nullptr; |
| |
988 |
| |
989 if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter) |
| |
990 return shape; |
| |
991 |
| |
992 /* |
| |
993 * Let JSObject::putProperty handle this |overwriting| case, including |
| |
994 * the conservation of shape->slot (if it's valid). We must not call |
| |
995 * removeProperty because it will free an allocated shape->slot, and |
| |
996 * putProperty won't re-allocate it. |
| |
997 */ |
| |
998 RootedId propid(cx, shape->propid()); |
| |
999 Shape *newShape = putProperty<mode>(cx, obj, propid, getter, setter, |
| |
1000 shape->maybeSlot(), attrs, shape->flags); |
| |
1001 |
| |
1002 obj->checkShapeConsistency(); |
| |
1003 return newShape; |
| |
1004 } |
| |
1005 |
| |
1006 template /* static */ Shape * |
| |
1007 JSObject::changeProperty<SequentialExecution>(ExclusiveContext *cx, |
| |
1008 HandleObject obj, HandleShape shape, |
| |
1009 unsigned attrs, unsigned mask, |
| |
1010 PropertyOp getter, StrictPropertyOp setter); |
| |
1011 template /* static */ Shape * |
| |
1012 JSObject::changeProperty<ParallelExecution>(ForkJoinContext *cx, |
| |
1013 HandleObject obj, HandleShape shape, |
| |
1014 unsigned attrs, unsigned mask, |
| |
1015 PropertyOp getter, StrictPropertyOp setter); |
| |
1016 |
| |
1017 bool |
| |
1018 JSObject::removeProperty(ExclusiveContext *cx, jsid id_) |
| |
1019 { |
| |
1020 RootedId id(cx, id_); |
| |
1021 RootedObject self(cx, this); |
| |
1022 |
| |
1023 Shape **spp; |
| |
1024 RootedShape shape(cx, Shape::search(cx, lastProperty(), id, &spp)); |
| |
1025 if (!shape) |
| |
1026 return true; |
| |
1027 |
| |
1028 /* |
| |
1029 * If shape is not the last property added, or the last property cannot |
| |
1030 * be removed, switch to dictionary mode. |
| |
1031 */ |
| |
1032 if (!self->inDictionaryMode() && (shape != self->lastProperty() || !self->canRemoveLastProperty())) { |
| |
1033 if (!self->toDictionaryMode(cx)) |
| |
1034 return false; |
| |
1035 spp = self->lastProperty()->table().search(shape->propid(), false); |
| |
1036 shape = SHAPE_FETCH(spp); |
| |
1037 } |
| |
1038 |
| |
1039 /* |
| |
1040 * If in dictionary mode, get a new shape for the last property after the |
| |
1041 * removal. We need a fresh shape for all dictionary deletions, even of |
| |
1042 * the last property. Otherwise, a shape could replay and caches might |
| |
1043 * return deleted DictionaryShapes! See bug 595365. Do this before changing |
| |
1044 * the object or table, so the remaining removal is infallible. |
| |
1045 */ |
| |
1046 RootedShape spare(cx); |
| |
1047 if (self->inDictionaryMode()) { |
| |
1048 spare = js_NewGCShape(cx); |
| |
1049 if (!spare) |
| |
1050 return false; |
| |
1051 new (spare) Shape(shape->base()->unowned(), 0); |
| |
1052 if (shape == self->lastProperty()) { |
| |
1053 /* |
| |
1054 * Get an up to date unowned base shape for the new last property |
| |
1055 * when removing the dictionary's last property. Information in |
| |
1056 * base shapes for non-last properties may be out of sync with the |
| |
1057 * object's state. |
| |
1058 */ |
| |
1059 RootedShape previous(cx, self->lastProperty()->parent); |
| |
1060 StackBaseShape base(self->lastProperty()->base()); |
| |
1061 base.updateGetterSetter(previous->attrs, previous->getter(), previous->setter()); |
| |
1062 BaseShape *nbase = BaseShape::getUnowned(cx, base); |
| |
1063 if (!nbase) |
| |
1064 return false; |
| |
1065 previous->base_ = nbase; |
| |
1066 } |
| |
1067 } |
| |
1068 |
| |
1069 /* If shape has a slot, free its slot number. */ |
| |
1070 if (shape->hasSlot()) { |
| |
1071 self->freeSlot(shape->slot()); |
| |
1072 if (cx->isJSContext()) |
| |
1073 ++cx->asJSContext()->runtime()->propertyRemovals; |
| |
1074 } |
| |
1075 |
| |
1076 /* |
| |
1077 * A dictionary-mode object owns mutable, unique shapes on a non-circular |
| |
1078 * doubly linked list, hashed by lastProperty()->table. So we can edit the |
| |
1079 * list and hash in place. |
| |
1080 */ |
| |
1081 if (self->inDictionaryMode()) { |
| |
1082 ShapeTable &table = self->lastProperty()->table(); |
| |
1083 |
| |
1084 if (SHAPE_HAD_COLLISION(*spp)) { |
| |
1085 *spp = SHAPE_REMOVED; |
| |
1086 ++table.removedCount; |
| |
1087 --table.entryCount; |
| |
1088 } else { |
| |
1089 *spp = nullptr; |
| |
1090 --table.entryCount; |
| |
1091 |
| |
1092 #ifdef DEBUG |
| |
1093 /* |
| |
1094 * Check the consistency of the table but limit the number of |
| |
1095 * checks not to alter significantly the complexity of the |
| |
1096 * delete in debug builds, see bug 534493. |
| |
1097 */ |
| |
1098 Shape *aprop = self->lastProperty(); |
| |
1099 for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent) |
| |
1100 JS_ASSERT_IF(aprop != shape, self->nativeContains(cx, aprop)); |
| |
1101 #endif |
| |
1102 } |
| |
1103 |
| |
1104 { |
| |
1105 /* Remove shape from its non-circular doubly linked list. */ |
| |
1106 Shape *oldLastProp = self->lastProperty(); |
| |
1107 shape->removeFromDictionary(self); |
| |
1108 |
| |
1109 /* Hand off table from the old to new last property. */ |
| |
1110 oldLastProp->handoffTableTo(self->lastProperty()); |
| |
1111 } |
| |
1112 |
| |
1113 /* Generate a new shape for the object, infallibly. */ |
| |
1114 JS_ALWAYS_TRUE(self->generateOwnShape(cx, spare)); |
| |
1115 |
| |
1116 /* Consider shrinking table if its load factor is <= .25. */ |
| |
1117 uint32_t size = table.capacity(); |
| |
1118 if (size > ShapeTable::MIN_SIZE && table.entryCount <= size >> 2) |
| |
1119 (void) table.change(-1, cx); |
| |
1120 } else { |
| |
1121 /* |
| |
1122 * Non-dictionary-mode shape tables are shared immutables, so all we |
| |
1123 * need do is retract the last property and we'll either get or else |
| |
1124 * lazily make via a later hashify the exact table for the new property |
| |
1125 * lineage. |
| |
1126 */ |
| |
1127 JS_ASSERT(shape == self->lastProperty()); |
| |
1128 self->removeLastProperty(cx); |
| |
1129 } |
| |
1130 |
| |
1131 self->checkShapeConsistency(); |
| |
1132 return true; |
| |
1133 } |
| |
1134 |
| |
1135 /* static */ void |
| |
1136 JSObject::clear(JSContext *cx, HandleObject obj) |
| |
1137 { |
| |
1138 RootedShape shape(cx, obj->lastProperty()); |
| |
1139 JS_ASSERT(obj->inDictionaryMode() == shape->inDictionary()); |
| |
1140 |
| |
1141 while (shape->parent) { |
| |
1142 shape = shape->parent; |
| |
1143 JS_ASSERT(obj->inDictionaryMode() == shape->inDictionary()); |
| |
1144 } |
| |
1145 JS_ASSERT(shape->isEmptyShape()); |
| |
1146 |
| |
1147 if (obj->inDictionaryMode()) |
| |
1148 shape->listp = &obj->shape_; |
| |
1149 |
| |
1150 JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, obj, shape)); |
| |
1151 |
| |
1152 ++cx->runtime()->propertyRemovals; |
| |
1153 obj->checkShapeConsistency(); |
| |
1154 } |
| |
1155 |
| |
1156 /* static */ bool |
| |
1157 JSObject::rollbackProperties(ExclusiveContext *cx, HandleObject obj, uint32_t slotSpan) |
| |
1158 { |
| |
1159 /* |
| |
1160 * Remove properties from this object until it has a matching slot span. |
| |
1161 * The object cannot have escaped in a way which would prevent safe |
| |
1162 * removal of the last properties. |
| |
1163 */ |
| |
1164 JS_ASSERT(!obj->inDictionaryMode() && slotSpan <= obj->slotSpan()); |
| |
1165 while (true) { |
| |
1166 if (obj->lastProperty()->isEmptyShape()) { |
| |
1167 JS_ASSERT(slotSpan == 0); |
| |
1168 break; |
| |
1169 } else { |
| |
1170 uint32_t slot = obj->lastProperty()->slot(); |
| |
1171 if (slot < slotSpan) |
| |
1172 break; |
| |
1173 JS_ASSERT(obj->getSlot(slot).isUndefined()); |
| |
1174 } |
| |
1175 if (!obj->removeProperty(cx, obj->lastProperty()->propid())) |
| |
1176 return false; |
| |
1177 } |
| |
1178 |
| |
1179 return true; |
| |
1180 } |
| |
1181 |
| |
1182 Shape * |
| |
1183 ObjectImpl::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape) |
| |
1184 { |
| |
1185 JS_ASSERT(cx->isThreadLocal(this)); |
| |
1186 JS_ASSERT(cx->isThreadLocal(oldShape)); |
| |
1187 JS_ASSERT(cx->isInsideCurrentCompartment(oldShape)); |
| |
1188 JS_ASSERT_IF(oldShape != lastProperty(), |
| |
1189 inDictionaryMode() && |
| |
1190 ((cx->isExclusiveContext() |
| |
1191 ? nativeLookup(cx->asExclusiveContext(), oldShape->propidRef()) |
| |
1192 : nativeLookupPure(oldShape->propidRef())) == oldShape)); |
| |
1193 |
| |
1194 ObjectImpl *self = this; |
| |
1195 |
| |
1196 if (!inDictionaryMode()) { |
| |
1197 Rooted<ObjectImpl*> selfRoot(cx, self); |
| |
1198 RootedShape newRoot(cx, newShape); |
| |
1199 if (!toDictionaryMode(cx)) |
| |
1200 return nullptr; |
| |
1201 oldShape = selfRoot->lastProperty(); |
| |
1202 self = selfRoot; |
| |
1203 newShape = newRoot; |
| |
1204 } |
| |
1205 |
| |
1206 if (!newShape) { |
| |
1207 Rooted<ObjectImpl*> selfRoot(cx, self); |
| |
1208 RootedShape oldRoot(cx, oldShape); |
| |
1209 newShape = js_NewGCShape(cx); |
| |
1210 if (!newShape) |
| |
1211 return nullptr; |
| |
1212 new (newShape) Shape(oldRoot->base()->unowned(), 0); |
| |
1213 self = selfRoot; |
| |
1214 oldShape = oldRoot; |
| |
1215 } |
| |
1216 |
| |
1217 ShapeTable &table = self->lastProperty()->table(); |
| |
1218 Shape **spp = oldShape->isEmptyShape() |
| |
1219 ? nullptr |
| |
1220 : table.search(oldShape->propidRef(), false); |
| |
1221 |
| |
1222 /* |
| |
1223 * Splice the new shape into the same position as the old shape, preserving |
| |
1224 * enumeration order (see bug 601399). |
| |
1225 */ |
| |
1226 StackShape nshape(oldShape); |
| |
1227 newShape->initDictionaryShape(nshape, self->numFixedSlots(), oldShape->listp); |
| |
1228 |
| |
1229 JS_ASSERT(newShape->parent == oldShape); |
| |
1230 oldShape->removeFromDictionary(self); |
| |
1231 |
| |
1232 if (newShape == self->lastProperty()) |
| |
1233 oldShape->handoffTableTo(newShape); |
| |
1234 |
| |
1235 if (spp) |
| |
1236 SHAPE_STORE_PRESERVING_COLLISION(spp, newShape); |
| |
1237 return newShape; |
| |
1238 } |
| |
1239 |
| |
1240 bool |
| |
1241 JSObject::shadowingShapeChange(ExclusiveContext *cx, const Shape &shape) |
| |
1242 { |
| |
1243 return generateOwnShape(cx); |
| |
1244 } |
| |
1245 |
| |
1246 /* static */ bool |
| |
1247 JSObject::clearParent(JSContext *cx, HandleObject obj) |
| |
1248 { |
| |
1249 return setParent(cx, obj, NullPtr()); |
| |
1250 } |
| |
1251 |
| |
1252 /* static */ bool |
| |
1253 JSObject::setParent(JSContext *cx, HandleObject obj, HandleObject parent) |
| |
1254 { |
| |
1255 if (parent && !parent->setDelegate(cx)) |
| |
1256 return false; |
| |
1257 |
| |
1258 if (obj->inDictionaryMode()) { |
| |
1259 StackBaseShape base(obj->lastProperty()); |
| |
1260 base.parent = parent; |
| |
1261 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); |
| |
1262 if (!nbase) |
| |
1263 return false; |
| |
1264 |
| |
1265 obj->lastProperty()->base()->adoptUnowned(nbase); |
| |
1266 return true; |
| |
1267 } |
| |
1268 |
| |
1269 Shape *newShape = Shape::setObjectParent(cx, parent, obj->getTaggedProto(), obj->shape_); |
| |
1270 if (!newShape) |
| |
1271 return false; |
| |
1272 |
| |
1273 obj->shape_ = newShape; |
| |
1274 return true; |
| |
1275 } |
| |
1276 |
| |
1277 /* static */ Shape * |
| |
1278 Shape::setObjectParent(ExclusiveContext *cx, JSObject *parent, TaggedProto proto, Shape *last) |
| |
1279 { |
| |
1280 if (last->getObjectParent() == parent) |
| |
1281 return last; |
| |
1282 |
| |
1283 StackBaseShape base(last); |
| |
1284 base.parent = parent; |
| |
1285 |
| |
1286 RootedShape lastRoot(cx, last); |
| |
1287 return replaceLastProperty(cx, base, proto, lastRoot); |
| |
1288 } |
| |
1289 |
| |
1290 /* static */ bool |
| |
1291 JSObject::setMetadata(JSContext *cx, HandleObject obj, HandleObject metadata) |
| |
1292 { |
| |
1293 if (obj->inDictionaryMode()) { |
| |
1294 StackBaseShape base(obj->lastProperty()); |
| |
1295 base.metadata = metadata; |
| |
1296 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); |
| |
1297 if (!nbase) |
| |
1298 return false; |
| |
1299 |
| |
1300 obj->lastProperty()->base()->adoptUnowned(nbase); |
| |
1301 return true; |
| |
1302 } |
| |
1303 |
| |
1304 Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), obj->shape_); |
| |
1305 if (!newShape) |
| |
1306 return false; |
| |
1307 |
| |
1308 obj->shape_ = newShape; |
| |
1309 return true; |
| |
1310 } |
| |
1311 |
| |
1312 /* static */ Shape * |
| |
1313 Shape::setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last) |
| |
1314 { |
| |
1315 if (last->getObjectMetadata() == metadata) |
| |
1316 return last; |
| |
1317 |
| |
1318 StackBaseShape base(last); |
| |
1319 base.metadata = metadata; |
| |
1320 |
| |
1321 RootedShape lastRoot(cx, last); |
| |
1322 return replaceLastProperty(cx, base, proto, lastRoot); |
| |
1323 } |
| |
1324 |
| |
1325 /* static */ bool |
| |
1326 js::ObjectImpl::preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj) |
| |
1327 { |
| |
1328 #ifdef DEBUG |
| |
1329 bool extensible; |
| |
1330 if (!JSObject::isExtensible(cx, obj, &extensible)) |
| |
1331 return false; |
| |
1332 MOZ_ASSERT(extensible, |
| |
1333 "Callers must ensure |obj| is extensible before calling " |
| |
1334 "preventExtensions"); |
| |
1335 #endif |
| |
1336 |
| |
1337 if (Downcast(obj)->is<ProxyObject>()) { |
| |
1338 RootedObject object(cx, obj->asObjectPtr()); |
| |
1339 return js::Proxy::preventExtensions(cx, object); |
| |
1340 } |
| |
1341 |
| |
1342 RootedObject self(cx, obj->asObjectPtr()); |
| |
1343 |
| |
1344 /* |
| |
1345 * Force lazy properties to be resolved by iterating over the objects' own |
| |
1346 * properties. |
| |
1347 */ |
| |
1348 AutoIdVector props(cx); |
| |
1349 if (!js::GetPropertyNames(cx, self, JSITER_HIDDEN | JSITER_OWNONLY, &props)) |
| |
1350 return false; |
| |
1351 |
| |
1352 /* |
| |
1353 * Convert all dense elements to sparse properties. This will shrink the |
| |
1354 * initialized length and capacity of the object to zero and ensure that no |
| |
1355 * new dense elements can be added without calling growElements(), which |
| |
1356 * checks isExtensible(). |
| |
1357 */ |
| |
1358 if (self->isNative() && !JSObject::sparsifyDenseElements(cx, self)) |
| |
1359 return false; |
| |
1360 |
| |
1361 return self->setFlag(cx, BaseShape::NOT_EXTENSIBLE, GENERATE_SHAPE); |
| |
1362 } |
| |
1363 |
| |
1364 bool |
| |
1365 js::ObjectImpl::setFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag_, |
| |
1366 GenerateShape generateShape) |
| |
1367 { |
| |
1368 BaseShape::Flag flag = (BaseShape::Flag) flag_; |
| |
1369 |
| |
1370 if (lastProperty()->getObjectFlags() & flag) |
| |
1371 return true; |
| |
1372 |
| |
1373 Rooted<ObjectImpl*> self(cx, this); |
| |
1374 |
| |
1375 if (inDictionaryMode()) { |
| |
1376 if (generateShape == GENERATE_SHAPE && !generateOwnShape(cx)) |
| |
1377 return false; |
| |
1378 StackBaseShape base(self->lastProperty()); |
| |
1379 base.flags |= flag; |
| |
1380 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); |
| |
1381 if (!nbase) |
| |
1382 return false; |
| |
1383 |
| |
1384 self->lastProperty()->base()->adoptUnowned(nbase); |
| |
1385 return true; |
| |
1386 } |
| |
1387 |
| |
1388 Shape *newShape = |
| |
1389 Shape::setObjectFlag(cx, flag, self->getTaggedProto(), self->lastProperty()); |
| |
1390 if (!newShape) |
| |
1391 return false; |
| |
1392 |
| |
1393 self->shape_ = newShape; |
| |
1394 return true; |
| |
1395 } |
| |
1396 |
| |
1397 bool |
| |
1398 js::ObjectImpl::clearFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag) |
| |
1399 { |
| |
1400 JS_ASSERT(inDictionaryMode()); |
| |
1401 JS_ASSERT(lastProperty()->getObjectFlags() & flag); |
| |
1402 |
| |
1403 RootedObject self(cx, this->asObjectPtr()); |
| |
1404 |
| |
1405 StackBaseShape base(self->lastProperty()); |
| |
1406 base.flags &= ~flag; |
| |
1407 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); |
| |
1408 if (!nbase) |
| |
1409 return false; |
| |
1410 |
| |
1411 self->lastProperty()->base()->adoptUnowned(nbase); |
| |
1412 return true; |
| |
1413 } |
| |
1414 |
| |
1415 /* static */ Shape * |
| |
1416 Shape::setObjectFlag(ExclusiveContext *cx, BaseShape::Flag flag, TaggedProto proto, Shape *last) |
| |
1417 { |
| |
1418 if (last->getObjectFlags() & flag) |
| |
1419 return last; |
| |
1420 |
| |
1421 StackBaseShape base(last); |
| |
1422 base.flags |= flag; |
| |
1423 |
| |
1424 RootedShape lastRoot(cx, last); |
| |
1425 return replaceLastProperty(cx, base, proto, lastRoot); |
| |
1426 } |
| |
1427 |
| |
1428 /* static */ inline HashNumber |
| |
1429 StackBaseShape::hash(const StackBaseShape *base) |
| |
1430 { |
| |
1431 HashNumber hash = base->flags; |
| |
1432 hash = RotateLeft(hash, 4) ^ (uintptr_t(base->clasp) >> 3); |
| |
1433 hash = RotateLeft(hash, 4) ^ (uintptr_t(base->parent) >> 3); |
| |
1434 hash = RotateLeft(hash, 4) ^ (uintptr_t(base->metadata) >> 3); |
| |
1435 hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawGetter); |
| |
1436 hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawSetter); |
| |
1437 return hash; |
| |
1438 } |
| |
1439 |
| |
1440 /* static */ inline bool |
| |
1441 StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup) |
| |
1442 { |
| |
1443 return key->flags == lookup->flags |
| |
1444 && key->clasp_ == lookup->clasp |
| |
1445 && key->parent == lookup->parent |
| |
1446 && key->metadata == lookup->metadata |
| |
1447 && key->rawGetter == lookup->rawGetter |
| |
1448 && key->rawSetter == lookup->rawSetter; |
| |
1449 } |
| |
1450 |
| |
1451 void |
| |
1452 StackBaseShape::trace(JSTracer *trc) |
| |
1453 { |
| |
1454 if (parent) { |
| |
1455 gc::MarkObjectRoot(trc, (JSObject**)&parent, |
| |
1456 "StackBaseShape parent"); |
| |
1457 } |
| |
1458 if (metadata) { |
| |
1459 gc::MarkObjectRoot(trc, (JSObject**)&metadata, |
| |
1460 "StackBaseShape metadata"); |
| |
1461 } |
| |
1462 if ((flags & BaseShape::HAS_GETTER_OBJECT) && rawGetter) { |
| |
1463 gc::MarkObjectRoot(trc, (JSObject**)&rawGetter, |
| |
1464 "StackBaseShape getter"); |
| |
1465 } |
| |
1466 if ((flags & BaseShape::HAS_SETTER_OBJECT) && rawSetter) { |
| |
1467 gc::MarkObjectRoot(trc, (JSObject**)&rawSetter, |
| |
1468 "StackBaseShape setter"); |
| |
1469 } |
| |
1470 } |
| |
1471 |
| |
1472 /* static */ UnownedBaseShape* |
| |
1473 BaseShape::getUnowned(ExclusiveContext *cx, StackBaseShape &base) |
| |
1474 { |
| |
1475 BaseShapeSet &table = cx->compartment()->baseShapes; |
| |
1476 |
| |
1477 if (!table.initialized() && !table.init()) |
| |
1478 return nullptr; |
| |
1479 |
| |
1480 DependentAddPtr<BaseShapeSet> p(cx, table, &base); |
| |
1481 if (p) |
| |
1482 return *p; |
| |
1483 |
| |
1484 RootedGeneric<StackBaseShape*> root(cx, &base); |
| |
1485 |
| |
1486 BaseShape *nbase_ = js_NewGCBaseShape<CanGC>(cx); |
| |
1487 if (!nbase_) |
| |
1488 return nullptr; |
| |
1489 |
| |
1490 new (nbase_) BaseShape(*root); |
| |
1491 |
| |
1492 UnownedBaseShape *nbase = static_cast<UnownedBaseShape *>(nbase_); |
| |
1493 |
| |
1494 if (!p.add(cx, table, root, nbase)) |
| |
1495 return nullptr; |
| |
1496 |
| |
1497 return nbase; |
| |
1498 } |
| |
1499 |
| |
1500 /* static */ UnownedBaseShape * |
| |
1501 BaseShape::lookupUnowned(ThreadSafeContext *cx, const StackBaseShape &base) |
| |
1502 { |
| |
1503 BaseShapeSet &table = cx->compartment_->baseShapes; |
| |
1504 |
| |
1505 if (!table.initialized()) |
| |
1506 return nullptr; |
| |
1507 |
| |
1508 BaseShapeSet::Ptr p = table.readonlyThreadsafeLookup(&base); |
| |
1509 return *p; |
| |
1510 } |
| |
1511 |
| |
1512 void |
| |
1513 BaseShape::assertConsistency() |
| |
1514 { |
| |
1515 #ifdef DEBUG |
| |
1516 if (isOwned()) { |
| |
1517 UnownedBaseShape *unowned = baseUnowned(); |
| |
1518 JS_ASSERT(hasGetterObject() == unowned->hasGetterObject()); |
| |
1519 JS_ASSERT(hasSetterObject() == unowned->hasSetterObject()); |
| |
1520 JS_ASSERT_IF(hasGetterObject(), getterObject() == unowned->getterObject()); |
| |
1521 JS_ASSERT_IF(hasSetterObject(), setterObject() == unowned->setterObject()); |
| |
1522 JS_ASSERT(getObjectParent() == unowned->getObjectParent()); |
| |
1523 JS_ASSERT(getObjectMetadata() == unowned->getObjectMetadata()); |
| |
1524 JS_ASSERT(getObjectFlags() == unowned->getObjectFlags()); |
| |
1525 } |
| |
1526 #endif |
| |
1527 } |
| |
1528 |
| |
1529 void |
| |
1530 JSCompartment::sweepBaseShapeTable() |
| |
1531 { |
| |
1532 gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, |
| |
1533 gcstats::PHASE_SWEEP_TABLES_BASE_SHAPE); |
| |
1534 |
| |
1535 if (baseShapes.initialized()) { |
| |
1536 for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) { |
| |
1537 UnownedBaseShape *base = e.front(); |
| |
1538 if (IsBaseShapeAboutToBeFinalized(&base)) |
| |
1539 e.removeFront(); |
| |
1540 } |
| |
1541 } |
| |
1542 } |
| |
1543 |
| |
1544 void |
| |
1545 BaseShape::finalize(FreeOp *fop) |
| |
1546 { |
| |
1547 if (table_) { |
| |
1548 fop->delete_(table_); |
| |
1549 table_ = nullptr; |
| |
1550 } |
| |
1551 } |
| |
1552 |
| |
1553 inline |
| |
1554 InitialShapeEntry::InitialShapeEntry() : shape(nullptr), proto(nullptr) |
| |
1555 { |
| |
1556 } |
| |
1557 |
| |
1558 inline |
| |
1559 InitialShapeEntry::InitialShapeEntry(const ReadBarriered<Shape> &shape, TaggedProto proto) |
| |
1560 : shape(shape), proto(proto) |
| |
1561 { |
| |
1562 } |
| |
1563 |
| |
1564 inline InitialShapeEntry::Lookup |
| |
1565 InitialShapeEntry::getLookup() const |
| |
1566 { |
| |
1567 return Lookup(shape->getObjectClass(), proto, shape->getObjectParent(), shape->getObjectMetadata(), |
| |
1568 shape->numFixedSlots(), shape->getObjectFlags()); |
| |
1569 } |
| |
1570 |
| |
1571 /* static */ inline HashNumber |
| |
1572 InitialShapeEntry::hash(const Lookup &lookup) |
| |
1573 { |
| |
1574 HashNumber hash = uintptr_t(lookup.clasp) >> 3; |
| |
1575 hash = RotateLeft(hash, 4) ^ |
| |
1576 (uintptr_t(lookup.hashProto.toWord()) >> 3); |
| |
1577 hash = RotateLeft(hash, 4) ^ |
| |
1578 (uintptr_t(lookup.hashParent) >> 3) ^ |
| |
1579 (uintptr_t(lookup.hashMetadata) >> 3); |
| |
1580 return hash + lookup.nfixed; |
| |
1581 } |
| |
1582 |
| |
1583 /* static */ inline bool |
| |
1584 InitialShapeEntry::match(const InitialShapeEntry &key, const Lookup &lookup) |
| |
1585 { |
| |
1586 const Shape *shape = *key.shape.unsafeGet(); |
| |
1587 return lookup.clasp == shape->getObjectClass() |
| |
1588 && lookup.matchProto.toWord() == key.proto.toWord() |
| |
1589 && lookup.matchParent == shape->getObjectParent() |
| |
1590 && lookup.matchMetadata == shape->getObjectMetadata() |
| |
1591 && lookup.nfixed == shape->numFixedSlots() |
| |
1592 && lookup.baseFlags == shape->getObjectFlags(); |
| |
1593 } |
| |
1594 |
| |
1595 #ifdef JSGC_GENERATIONAL |
| |
1596 |
| |
1597 /* |
| |
1598 * This class is used to add a post barrier on the initialShapes set, as the key |
| |
1599 * is calculated based on several objects which may be moved by generational GC. |
| |
1600 */ |
| |
1601 class InitialShapeSetRef : public BufferableRef |
| |
1602 { |
| |
1603 InitialShapeSet *set; |
| |
1604 const Class *clasp; |
| |
1605 TaggedProto proto; |
| |
1606 JSObject *parent; |
| |
1607 JSObject *metadata; |
| |
1608 size_t nfixed; |
| |
1609 uint32_t objectFlags; |
| |
1610 |
| |
1611 public: |
| |
1612 InitialShapeSetRef(InitialShapeSet *set, |
| |
1613 const Class *clasp, |
| |
1614 TaggedProto proto, |
| |
1615 JSObject *parent, |
| |
1616 JSObject *metadata, |
| |
1617 size_t nfixed, |
| |
1618 uint32_t objectFlags) |
| |
1619 : set(set), |
| |
1620 clasp(clasp), |
| |
1621 proto(proto), |
| |
1622 parent(parent), |
| |
1623 metadata(metadata), |
| |
1624 nfixed(nfixed), |
| |
1625 objectFlags(objectFlags) |
| |
1626 {} |
| |
1627 |
| |
1628 void mark(JSTracer *trc) { |
| |
1629 TaggedProto priorProto = proto; |
| |
1630 JSObject *priorParent = parent; |
| |
1631 JSObject *priorMetadata = metadata; |
| |
1632 if (proto.isObject()) |
| |
1633 Mark(trc, reinterpret_cast<JSObject**>(&proto), "initialShapes set proto"); |
| |
1634 if (parent) |
| |
1635 Mark(trc, &parent, "initialShapes set parent"); |
| |
1636 if (metadata) |
| |
1637 Mark(trc, &metadata, "initialShapes set metadata"); |
| |
1638 if (proto == priorProto && parent == priorParent && metadata == priorMetadata) |
| |
1639 return; |
| |
1640 |
| |
1641 /* Find the original entry, which must still be present. */ |
| |
1642 InitialShapeEntry::Lookup lookup(clasp, priorProto, |
| |
1643 priorParent, parent, |
| |
1644 priorMetadata, metadata, |
| |
1645 nfixed, objectFlags); |
| |
1646 InitialShapeSet::Ptr p = set->lookup(lookup); |
| |
1647 JS_ASSERT(p); |
| |
1648 |
| |
1649 /* Update the entry's possibly-moved proto, and ensure lookup will still match. */ |
| |
1650 InitialShapeEntry &entry = const_cast<InitialShapeEntry&>(*p); |
| |
1651 entry.proto = proto; |
| |
1652 lookup.matchProto = proto; |
| |
1653 |
| |
1654 /* Rekey the entry. */ |
| |
1655 set->rekeyAs(lookup, |
| |
1656 InitialShapeEntry::Lookup(clasp, proto, parent, metadata, nfixed, objectFlags), |
| |
1657 *p); |
| |
1658 } |
| |
1659 }; |
| |
1660 |
| |
1661 #ifdef JS_GC_ZEAL |
| |
1662 void |
| |
1663 JSCompartment::checkInitialShapesTableAfterMovingGC() |
| |
1664 { |
| |
1665 if (!initialShapes.initialized()) |
| |
1666 return; |
| |
1667 |
| |
1668 /* |
| |
1669 * Assert that the postbarriers have worked and that nothing is left in |
| |
1670 * initialShapes that points into the nursery, and that the hash table |
| |
1671 * entries are discoverable. |
| |
1672 */ |
| |
1673 JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtimeFromMainThread()); |
| |
1674 for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { |
| |
1675 InitialShapeEntry entry = e.front(); |
| |
1676 TaggedProto proto = entry.proto; |
| |
1677 Shape *shape = entry.shape.get(); |
| |
1678 |
| |
1679 JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(rt, proto.toObject())); |
| |
1680 JS_ASSERT(!IsInsideNursery(rt, shape->getObjectParent())); |
| |
1681 JS_ASSERT(!IsInsideNursery(rt, shape->getObjectMetadata())); |
| |
1682 |
| |
1683 InitialShapeEntry::Lookup lookup(shape->getObjectClass(), |
| |
1684 proto, |
| |
1685 shape->getObjectParent(), |
| |
1686 shape->getObjectMetadata(), |
| |
1687 shape->numFixedSlots(), |
| |
1688 shape->getObjectFlags()); |
| |
1689 InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup); |
| |
1690 JS_ASSERT(ptr.found() && &*ptr == &e.front()); |
| |
1691 } |
| |
1692 } |
| |
1693 #endif |
| |
1694 |
| |
1695 #endif |
| |
1696 |
| |
1697 /* static */ Shape * |
| |
1698 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto, |
| |
1699 JSObject *parent, JSObject *metadata, |
| |
1700 size_t nfixed, uint32_t objectFlags) |
| |
1701 { |
| |
1702 JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); |
| |
1703 JS_ASSERT_IF(parent, cx->isInsideCurrentCompartment(parent)); |
| |
1704 |
| |
1705 InitialShapeSet &table = cx->compartment()->initialShapes; |
| |
1706 |
| |
1707 if (!table.initialized() && !table.init()) |
| |
1708 return nullptr; |
| |
1709 |
| |
1710 typedef InitialShapeEntry::Lookup Lookup; |
| |
1711 DependentAddPtr<InitialShapeSet> |
| |
1712 p(cx, table, Lookup(clasp, proto, parent, metadata, nfixed, objectFlags)); |
| |
1713 if (p) |
| |
1714 return p->shape; |
| |
1715 |
| |
1716 Rooted<TaggedProto> protoRoot(cx, proto); |
| |
1717 RootedObject parentRoot(cx, parent); |
| |
1718 RootedObject metadataRoot(cx, metadata); |
| |
1719 |
| |
1720 StackBaseShape base(cx, clasp, parent, metadata, objectFlags); |
| |
1721 Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base)); |
| |
1722 if (!nbase) |
| |
1723 return nullptr; |
| |
1724 |
| |
1725 Shape *shape = cx->compartment()->propertyTree.newShape(cx); |
| |
1726 if (!shape) |
| |
1727 return nullptr; |
| |
1728 new (shape) EmptyShape(nbase, nfixed); |
| |
1729 |
| |
1730 Lookup lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags); |
| |
1731 if (!p.add(cx, table, lookup, InitialShapeEntry(shape, protoRoot))) |
| |
1732 return nullptr; |
| |
1733 |
| |
1734 #ifdef JSGC_GENERATIONAL |
| |
1735 if (cx->hasNursery()) { |
| |
1736 if ((protoRoot.isObject() && cx->nursery().isInside(protoRoot.toObject())) || |
| |
1737 cx->nursery().isInside(parentRoot.get()) || |
| |
1738 cx->nursery().isInside(metadataRoot.get())) |
| |
1739 { |
| |
1740 InitialShapeSetRef ref( |
| |
1741 &table, clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags); |
| |
1742 cx->asJSContext()->runtime()->gcStoreBuffer.putGeneric(ref); |
| |
1743 } |
| |
1744 } |
| |
1745 #endif |
| |
1746 |
| |
1747 return shape; |
| |
1748 } |
| |
1749 |
| |
1750 /* static */ Shape * |
| |
1751 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto, |
| |
1752 JSObject *parent, JSObject *metadata, |
| |
1753 AllocKind kind, uint32_t objectFlags) |
| |
1754 { |
| |
1755 return getInitialShape(cx, clasp, proto, parent, metadata, GetGCKindSlots(kind, clasp), objectFlags); |
| |
1756 } |
| |
1757 |
| |
1758 void |
| |
1759 NewObjectCache::invalidateEntriesForShape(JSContext *cx, HandleShape shape, HandleObject proto) |
| |
1760 { |
| |
1761 const Class *clasp = shape->getObjectClass(); |
| |
1762 |
| |
1763 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| |
1764 if (CanBeFinalizedInBackground(kind, clasp)) |
| |
1765 kind = GetBackgroundAllocKind(kind); |
| |
1766 |
| |
1767 Rooted<GlobalObject *> global(cx, &shape->getObjectParent()->global()); |
| |
1768 Rooted<types::TypeObject *> type(cx, cx->getNewType(clasp, proto.get())); |
| |
1769 |
| |
1770 EntryIndex entry; |
| |
1771 if (lookupGlobal(clasp, global, kind, &entry)) |
| |
1772 PodZero(&entries[entry]); |
| |
1773 if (!proto->is<GlobalObject>() && lookupProto(clasp, proto, kind, &entry)) |
| |
1774 PodZero(&entries[entry]); |
| |
1775 if (lookupType(type, kind, &entry)) |
| |
1776 PodZero(&entries[entry]); |
| |
1777 } |
| |
1778 |
| |
1779 /* static */ void |
| |
1780 EmptyShape::insertInitialShape(ExclusiveContext *cx, HandleShape shape, HandleObject proto) |
| |
1781 { |
| |
1782 InitialShapeEntry::Lookup lookup(shape->getObjectClass(), TaggedProto(proto), |
| |
1783 shape->getObjectParent(), shape->getObjectMetadata(), |
| |
1784 shape->numFixedSlots(), shape->getObjectFlags()); |
| |
1785 |
| |
1786 InitialShapeSet::Ptr p = cx->compartment()->initialShapes.lookup(lookup); |
| |
1787 JS_ASSERT(p); |
| |
1788 |
| |
1789 InitialShapeEntry &entry = const_cast<InitialShapeEntry &>(*p); |
| |
1790 |
| |
1791 /* The new shape had better be rooted at the old one. */ |
| |
1792 #ifdef DEBUG |
| |
1793 Shape *nshape = shape; |
| |
1794 while (!nshape->isEmptyShape()) |
| |
1795 nshape = nshape->previous(); |
| |
1796 JS_ASSERT(nshape == entry.shape); |
| |
1797 #endif |
| |
1798 |
| |
1799 entry.shape = shape.get(); |
| |
1800 |
| |
1801 /* |
| |
1802 * This affects the shape that will be produced by the various NewObject |
| |
1803 * methods, so clear any cache entry referring to the old shape. This is |
| |
1804 * not required for correctness: the NewObject must always check for a |
| |
1805 * nativeEmpty() result and generate the appropriate properties if found. |
| |
1806 * Clearing the cache entry avoids this duplicate regeneration. |
| |
1807 * |
| |
1808 * Clearing is not necessary when this context is running off the main |
| |
1809 * thread, as it will not use the new object cache for allocations. |
| |
1810 */ |
| |
1811 if (cx->isJSContext()) { |
| |
1812 JSContext *ncx = cx->asJSContext(); |
| |
1813 ncx->runtime()->newObjectCache.invalidateEntriesForShape(ncx, shape, proto); |
| |
1814 } |
| |
1815 } |
| |
1816 |
| |
1817 void |
| |
1818 JSCompartment::sweepInitialShapeTable() |
| |
1819 { |
| |
1820 gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, |
| |
1821 gcstats::PHASE_SWEEP_TABLES_INITIAL_SHAPE); |
| |
1822 |
| |
1823 if (initialShapes.initialized()) { |
| |
1824 for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { |
| |
1825 const InitialShapeEntry &entry = e.front(); |
| |
1826 Shape *shape = entry.shape; |
| |
1827 JSObject *proto = entry.proto.raw(); |
| |
1828 if (IsShapeAboutToBeFinalized(&shape) || (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) { |
| |
1829 e.removeFront(); |
| |
1830 } else { |
| |
1831 #ifdef DEBUG |
| |
1832 DebugOnly<JSObject *> parent = shape->getObjectParent(); |
| |
1833 JS_ASSERT(!parent || !IsObjectAboutToBeFinalized(&parent)); |
| |
1834 JS_ASSERT(parent == shape->getObjectParent()); |
| |
1835 #endif |
| |
1836 if (shape != entry.shape || proto != entry.proto.raw()) { |
| |
1837 InitialShapeEntry newKey(shape, proto); |
| |
1838 e.rekeyFront(newKey.getLookup(), newKey); |
| |
1839 } |
| |
1840 } |
| |
1841 } |
| |
1842 } |
| |
1843 } |
| |
1844 |
| |
1845 void |
| |
1846 AutoRooterGetterSetter::Inner::trace(JSTracer *trc) |
| |
1847 { |
| |
1848 if ((attrs & JSPROP_GETTER) && *pgetter) |
| |
1849 gc::MarkObjectRoot(trc, (JSObject**) pgetter, "AutoRooterGetterSetter getter"); |
| |
1850 if ((attrs & JSPROP_SETTER) && *psetter) |
| |
1851 gc::MarkObjectRoot(trc, (JSObject**) psetter, "AutoRooterGetterSetter setter"); |
| |
1852 } |