|
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/ObjectImpl-inl.h" |
|
8 |
|
9 #include "gc/Marking.h" |
|
10 #include "js/Value.h" |
|
11 #include "vm/Debugger.h" |
|
12 |
|
13 #include "jsobjinlines.h" |
|
14 #include "vm/Shape-inl.h" |
|
15 |
|
16 using namespace js; |
|
17 |
|
18 using JS::GenericNaN; |
|
19 |
|
20 PropDesc::PropDesc() |
|
21 : pd_(UndefinedValue()), |
|
22 value_(UndefinedValue()), |
|
23 get_(UndefinedValue()), |
|
24 set_(UndefinedValue()), |
|
25 attrs(0), |
|
26 hasGet_(false), |
|
27 hasSet_(false), |
|
28 hasValue_(false), |
|
29 hasWritable_(false), |
|
30 hasEnumerable_(false), |
|
31 hasConfigurable_(false), |
|
32 isUndefined_(true) |
|
33 { |
|
34 } |
|
35 |
|
36 bool |
|
37 PropDesc::checkGetter(JSContext *cx) |
|
38 { |
|
39 if (hasGet_) { |
|
40 if (!js_IsCallable(get_) && !get_.isUndefined()) { |
|
41 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD, |
|
42 js_getter_str); |
|
43 return false; |
|
44 } |
|
45 } |
|
46 return true; |
|
47 } |
|
48 |
|
49 bool |
|
50 PropDesc::checkSetter(JSContext *cx) |
|
51 { |
|
52 if (hasSet_) { |
|
53 if (!js_IsCallable(set_) && !set_.isUndefined()) { |
|
54 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD, |
|
55 js_setter_str); |
|
56 return false; |
|
57 } |
|
58 } |
|
59 return true; |
|
60 } |
|
61 |
|
62 static bool |
|
63 CheckArgCompartment(JSContext *cx, JSObject *obj, HandleValue v, |
|
64 const char *methodname, const char *propname) |
|
65 { |
|
66 if (v.isObject() && v.toObject().compartment() != obj->compartment()) { |
|
67 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_COMPARTMENT_MISMATCH, |
|
68 methodname, propname); |
|
69 return false; |
|
70 } |
|
71 return true; |
|
72 } |
|
73 |
|
74 /* |
|
75 * Convert Debugger.Objects in desc to debuggee values. |
|
76 * Reject non-callable getters and setters. |
|
77 */ |
|
78 bool |
|
79 PropDesc::unwrapDebuggerObjectsInto(JSContext *cx, Debugger *dbg, HandleObject obj, |
|
80 PropDesc *unwrapped) const |
|
81 { |
|
82 MOZ_ASSERT(!isUndefined()); |
|
83 |
|
84 *unwrapped = *this; |
|
85 |
|
86 if (unwrapped->hasValue()) { |
|
87 RootedValue value(cx, unwrapped->value_); |
|
88 if (!dbg->unwrapDebuggeeValue(cx, &value) || |
|
89 !CheckArgCompartment(cx, obj, value, "defineProperty", "value")) |
|
90 { |
|
91 return false; |
|
92 } |
|
93 unwrapped->value_ = value; |
|
94 } |
|
95 |
|
96 if (unwrapped->hasGet()) { |
|
97 RootedValue get(cx, unwrapped->get_); |
|
98 if (!dbg->unwrapDebuggeeValue(cx, &get) || |
|
99 !CheckArgCompartment(cx, obj, get, "defineProperty", "get")) |
|
100 { |
|
101 return false; |
|
102 } |
|
103 unwrapped->get_ = get; |
|
104 } |
|
105 |
|
106 if (unwrapped->hasSet()) { |
|
107 RootedValue set(cx, unwrapped->set_); |
|
108 if (!dbg->unwrapDebuggeeValue(cx, &set) || |
|
109 !CheckArgCompartment(cx, obj, set, "defineProperty", "set")) |
|
110 { |
|
111 return false; |
|
112 } |
|
113 unwrapped->set_ = set; |
|
114 } |
|
115 |
|
116 return true; |
|
117 } |
|
118 |
|
119 /* |
|
120 * Rewrap *idp and the fields of *desc for the current compartment. Also: |
|
121 * defining a property on a proxy requires pd_ to contain a descriptor object, |
|
122 * so reconstitute desc->pd_ if needed. |
|
123 */ |
|
124 bool |
|
125 PropDesc::wrapInto(JSContext *cx, HandleObject obj, const jsid &id, jsid *wrappedId, |
|
126 PropDesc *desc) const |
|
127 { |
|
128 MOZ_ASSERT(!isUndefined()); |
|
129 |
|
130 JSCompartment *comp = cx->compartment(); |
|
131 |
|
132 *wrappedId = id; |
|
133 if (!comp->wrapId(cx, wrappedId)) |
|
134 return false; |
|
135 |
|
136 *desc = *this; |
|
137 RootedValue value(cx, desc->value_); |
|
138 RootedValue get(cx, desc->get_); |
|
139 RootedValue set(cx, desc->set_); |
|
140 |
|
141 if (!comp->wrap(cx, &value) || !comp->wrap(cx, &get) || !comp->wrap(cx, &set)) |
|
142 return false; |
|
143 |
|
144 desc->value_ = value; |
|
145 desc->get_ = get; |
|
146 desc->set_ = set; |
|
147 return !obj->is<ProxyObject>() || desc->makeObject(cx); |
|
148 } |
|
149 |
|
150 static const ObjectElements emptyElementsHeader(0, 0); |
|
151 |
|
152 /* Objects with no elements share one empty set of elements. */ |
|
153 HeapSlot *const js::emptyObjectElements = |
|
154 reinterpret_cast<HeapSlot *>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements)); |
|
155 |
|
156 #ifdef DEBUG |
|
157 |
|
158 bool |
|
159 ObjectImpl::canHaveNonEmptyElements() |
|
160 { |
|
161 JSObject *obj = static_cast<JSObject *>(this); |
|
162 return isNative() && !obj->is<TypedArrayObject>(); |
|
163 } |
|
164 |
|
165 #endif // DEBUG |
|
166 |
|
167 /* static */ bool |
|
168 ObjectElements::ConvertElementsToDoubles(JSContext *cx, uintptr_t elementsPtr) |
|
169 { |
|
170 /* |
|
171 * This function is infallible, but has a fallible interface so that it can |
|
172 * be called directly from Ion code. Only arrays can have their dense |
|
173 * elements converted to doubles, and arrays never have empty elements. |
|
174 */ |
|
175 HeapSlot *elementsHeapPtr = (HeapSlot *) elementsPtr; |
|
176 JS_ASSERT(elementsHeapPtr != emptyObjectElements); |
|
177 |
|
178 ObjectElements *header = ObjectElements::fromElements(elementsHeapPtr); |
|
179 JS_ASSERT(!header->shouldConvertDoubleElements()); |
|
180 |
|
181 Value *vp = (Value *) elementsPtr; |
|
182 for (size_t i = 0; i < header->initializedLength; i++) { |
|
183 if (vp[i].isInt32()) |
|
184 vp[i].setDouble(vp[i].toInt32()); |
|
185 } |
|
186 |
|
187 header->setShouldConvertDoubleElements(); |
|
188 return true; |
|
189 } |
|
190 |
|
191 #ifdef DEBUG |
|
192 void |
|
193 js::ObjectImpl::checkShapeConsistency() |
|
194 { |
|
195 static int throttle = -1; |
|
196 if (throttle < 0) { |
|
197 if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE")) |
|
198 throttle = atoi(var); |
|
199 if (throttle < 0) |
|
200 throttle = 0; |
|
201 } |
|
202 if (throttle == 0) |
|
203 return; |
|
204 |
|
205 MOZ_ASSERT(isNative()); |
|
206 |
|
207 Shape *shape = lastProperty(); |
|
208 Shape *prev = nullptr; |
|
209 |
|
210 if (inDictionaryMode()) { |
|
211 MOZ_ASSERT(shape->hasTable()); |
|
212 |
|
213 ShapeTable &table = shape->table(); |
|
214 for (uint32_t fslot = table.freelist; fslot != SHAPE_INVALID_SLOT; |
|
215 fslot = getSlot(fslot).toPrivateUint32()) { |
|
216 MOZ_ASSERT(fslot < slotSpan()); |
|
217 } |
|
218 |
|
219 for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) { |
|
220 MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable()); |
|
221 |
|
222 Shape **spp = table.search(shape->propid(), false); |
|
223 MOZ_ASSERT(SHAPE_FETCH(spp) == shape); |
|
224 } |
|
225 |
|
226 shape = lastProperty(); |
|
227 for (int n = throttle; --n >= 0 && shape; shape = shape->parent) { |
|
228 MOZ_ASSERT_IF(shape->slot() != SHAPE_INVALID_SLOT, shape->slot() < slotSpan()); |
|
229 if (!prev) { |
|
230 MOZ_ASSERT(lastProperty() == shape); |
|
231 MOZ_ASSERT(shape->listp == &shape_); |
|
232 } else { |
|
233 MOZ_ASSERT(shape->listp == &prev->parent); |
|
234 } |
|
235 prev = shape; |
|
236 } |
|
237 } else { |
|
238 for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) { |
|
239 if (shape->hasTable()) { |
|
240 ShapeTable &table = shape->table(); |
|
241 MOZ_ASSERT(shape->parent); |
|
242 for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) { |
|
243 Shape **spp = table.search(r.front().propid(), false); |
|
244 MOZ_ASSERT(SHAPE_FETCH(spp) == &r.front()); |
|
245 } |
|
246 } |
|
247 if (prev) { |
|
248 MOZ_ASSERT(prev->maybeSlot() >= shape->maybeSlot()); |
|
249 shape->kids.checkConsistency(prev); |
|
250 } |
|
251 prev = shape; |
|
252 } |
|
253 } |
|
254 } |
|
255 #endif |
|
256 |
|
257 void |
|
258 js::ObjectImpl::initializeSlotRange(uint32_t start, uint32_t length) |
|
259 { |
|
260 /* |
|
261 * No bounds check, as this is used when the object's shape does not |
|
262 * reflect its allocated slots (updateSlotsForSpan). |
|
263 */ |
|
264 HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; |
|
265 getSlotRangeUnchecked(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); |
|
266 |
|
267 JSRuntime *rt = runtimeFromAnyThread(); |
|
268 uint32_t offset = start; |
|
269 for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++) |
|
270 sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue()); |
|
271 for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++) |
|
272 sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue()); |
|
273 } |
|
274 |
|
275 void |
|
276 js::ObjectImpl::initSlotRange(uint32_t start, const Value *vector, uint32_t length) |
|
277 { |
|
278 JSRuntime *rt = runtimeFromAnyThread(); |
|
279 HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; |
|
280 getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); |
|
281 for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++) |
|
282 sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); |
|
283 for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++) |
|
284 sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); |
|
285 } |
|
286 |
|
287 void |
|
288 js::ObjectImpl::copySlotRange(uint32_t start, const Value *vector, uint32_t length) |
|
289 { |
|
290 JS::Zone *zone = this->zone(); |
|
291 HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd; |
|
292 getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); |
|
293 for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++) |
|
294 sp->set(zone, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); |
|
295 for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++) |
|
296 sp->set(zone, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++); |
|
297 } |
|
298 |
|
299 #ifdef DEBUG |
|
300 bool |
|
301 js::ObjectImpl::isProxy() const |
|
302 { |
|
303 return asObjectPtr()->is<ProxyObject>(); |
|
304 } |
|
305 |
|
306 bool |
|
307 js::ObjectImpl::slotInRange(uint32_t slot, SentinelAllowed sentinel) const |
|
308 { |
|
309 uint32_t capacity = numFixedSlots() + numDynamicSlots(); |
|
310 if (sentinel == SENTINEL_ALLOWED) |
|
311 return slot <= capacity; |
|
312 return slot < capacity; |
|
313 } |
|
314 #endif /* DEBUG */ |
|
315 |
|
316 // See bug 844580. |
|
317 #if defined(_MSC_VER) |
|
318 # pragma optimize("g", off) |
|
319 #endif |
|
320 |
|
321 #if defined(_MSC_VER) && _MSC_VER >= 1500 |
|
322 /* |
|
323 * Work around a compiler bug in MSVC9 and above, where inlining this function |
|
324 * causes stack pointer offsets to go awry and spp to refer to something higher |
|
325 * up the stack. |
|
326 */ |
|
327 MOZ_NEVER_INLINE |
|
328 #endif |
|
329 Shape * |
|
330 js::ObjectImpl::nativeLookup(ExclusiveContext *cx, jsid id) |
|
331 { |
|
332 MOZ_ASSERT(isNative()); |
|
333 Shape **spp; |
|
334 return Shape::search(cx, lastProperty(), id, &spp); |
|
335 } |
|
336 |
|
337 #if defined(_MSC_VER) |
|
338 # pragma optimize("", on) |
|
339 #endif |
|
340 |
|
341 Shape * |
|
342 js::ObjectImpl::nativeLookupPure(jsid id) |
|
343 { |
|
344 MOZ_ASSERT(isNative()); |
|
345 return Shape::searchNoHashify(lastProperty(), id); |
|
346 } |
|
347 |
|
348 uint32_t |
|
349 js::ObjectImpl::dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class *clasp) |
|
350 { |
|
351 if (span <= nfixed) |
|
352 return 0; |
|
353 span -= nfixed; |
|
354 |
|
355 // Increase the slots to SLOT_CAPACITY_MIN to decrease the likelihood |
|
356 // the dynamic slots need to get increased again. ArrayObjects ignore |
|
357 // this because slots are uncommon in that case. |
|
358 if (clasp != &ArrayObject::class_ && span <= SLOT_CAPACITY_MIN) |
|
359 return SLOT_CAPACITY_MIN; |
|
360 |
|
361 uint32_t slots = mozilla::RoundUpPow2(span); |
|
362 MOZ_ASSERT(slots >= span); |
|
363 return slots; |
|
364 } |
|
365 |
|
366 void |
|
367 js::ObjectImpl::markChildren(JSTracer *trc) |
|
368 { |
|
369 MarkTypeObject(trc, &type_, "type"); |
|
370 |
|
371 MarkShape(trc, &shape_, "shape"); |
|
372 |
|
373 const Class *clasp = type_->clasp(); |
|
374 JSObject *obj = asObjectPtr(); |
|
375 if (clasp->trace) |
|
376 clasp->trace(trc, obj); |
|
377 |
|
378 if (shape_->isNative()) { |
|
379 MarkObjectSlots(trc, obj, 0, obj->slotSpan()); |
|
380 gc::MarkArraySlots(trc, obj->getDenseInitializedLength(), obj->getDenseElements(), "objectElements"); |
|
381 } |
|
382 } |
|
383 |
|
384 void |
|
385 AutoPropDescRooter::trace(JSTracer *trc) |
|
386 { |
|
387 gc::MarkValueRoot(trc, &propDesc.pd_, "AutoPropDescRooter pd"); |
|
388 gc::MarkValueRoot(trc, &propDesc.value_, "AutoPropDescRooter value"); |
|
389 gc::MarkValueRoot(trc, &propDesc.get_, "AutoPropDescRooter get"); |
|
390 gc::MarkValueRoot(trc, &propDesc.set_, "AutoPropDescRooter set"); |
|
391 } |