|
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 /* |
|
8 * JS object implementation. |
|
9 */ |
|
10 |
|
11 #include "jsobjinlines.h" |
|
12 |
|
13 #include "mozilla/ArrayUtils.h" |
|
14 #include "mozilla/MathAlgorithms.h" |
|
15 #include "mozilla/MemoryReporting.h" |
|
16 #include "mozilla/TemplateLib.h" |
|
17 |
|
18 #include <string.h> |
|
19 |
|
20 #include "jsapi.h" |
|
21 #include "jsarray.h" |
|
22 #include "jsatom.h" |
|
23 #include "jscntxt.h" |
|
24 #include "jsfriendapi.h" |
|
25 #include "jsfun.h" |
|
26 #include "jsgc.h" |
|
27 #include "jsiter.h" |
|
28 #include "jsnum.h" |
|
29 #include "jsopcode.h" |
|
30 #include "jsprf.h" |
|
31 #include "jsproxy.h" |
|
32 #include "jsscript.h" |
|
33 #include "jsstr.h" |
|
34 #include "jstypes.h" |
|
35 #include "jsutil.h" |
|
36 #include "jswatchpoint.h" |
|
37 #include "jswrapper.h" |
|
38 |
|
39 #include "builtin/Object.h" |
|
40 #include "frontend/BytecodeCompiler.h" |
|
41 #include "gc/Marking.h" |
|
42 #include "jit/AsmJSModule.h" |
|
43 #include "jit/BaselineJIT.h" |
|
44 #include "js/MemoryMetrics.h" |
|
45 #include "js/OldDebugAPI.h" |
|
46 #include "vm/ArgumentsObject.h" |
|
47 #include "vm/Interpreter.h" |
|
48 #include "vm/ProxyObject.h" |
|
49 #include "vm/RegExpStaticsObject.h" |
|
50 #include "vm/Shape.h" |
|
51 |
|
52 #include "jsatominlines.h" |
|
53 #include "jsboolinlines.h" |
|
54 #include "jscntxtinlines.h" |
|
55 #include "jscompartmentinlines.h" |
|
56 |
|
57 #include "vm/ArrayObject-inl.h" |
|
58 #include "vm/BooleanObject-inl.h" |
|
59 #include "vm/NumberObject-inl.h" |
|
60 #include "vm/ObjectImpl-inl.h" |
|
61 #include "vm/Runtime-inl.h" |
|
62 #include "vm/Shape-inl.h" |
|
63 #include "vm/StringObject-inl.h" |
|
64 |
|
65 using namespace js; |
|
66 using namespace js::gc; |
|
67 using namespace js::types; |
|
68 |
|
69 using mozilla::Maybe; |
|
70 using mozilla::RoundUpPow2; |
|
71 |
|
72 JS_STATIC_ASSERT(int32_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value)) == int64_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value))); |
|
73 |
|
74 const Class JSObject::class_ = { |
|
75 js_Object_str, |
|
76 JSCLASS_HAS_CACHED_PROTO(JSProto_Object), |
|
77 JS_PropertyStub, /* addProperty */ |
|
78 JS_DeletePropertyStub, /* delProperty */ |
|
79 JS_PropertyStub, /* getProperty */ |
|
80 JS_StrictPropertyStub, /* setProperty */ |
|
81 JS_EnumerateStub, |
|
82 JS_ResolveStub, |
|
83 JS_ConvertStub |
|
84 }; |
|
85 |
|
86 const Class* const js::ObjectClassPtr = &JSObject::class_; |
|
87 |
|
88 JS_FRIEND_API(JSObject *) |
|
89 JS_ObjectToInnerObject(JSContext *cx, HandleObject obj) |
|
90 { |
|
91 if (!obj) { |
|
92 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INACTIVE); |
|
93 return nullptr; |
|
94 } |
|
95 return GetInnerObject(cx, obj); |
|
96 } |
|
97 |
|
98 JS_FRIEND_API(JSObject *) |
|
99 JS_ObjectToOuterObject(JSContext *cx, HandleObject obj) |
|
100 { |
|
101 assertSameCompartment(cx, obj); |
|
102 return GetOuterObject(cx, obj); |
|
103 } |
|
104 |
|
105 JSObject * |
|
106 js::NonNullObject(JSContext *cx, const Value &v) |
|
107 { |
|
108 if (v.isPrimitive()) { |
|
109 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); |
|
110 return nullptr; |
|
111 } |
|
112 return &v.toObject(); |
|
113 } |
|
114 |
|
115 const char * |
|
116 js::InformalValueTypeName(const Value &v) |
|
117 { |
|
118 if (v.isObject()) |
|
119 return v.toObject().getClass()->name; |
|
120 if (v.isString()) |
|
121 return "string"; |
|
122 if (v.isNumber()) |
|
123 return "number"; |
|
124 if (v.isBoolean()) |
|
125 return "boolean"; |
|
126 if (v.isNull()) |
|
127 return "null"; |
|
128 if (v.isUndefined()) |
|
129 return "undefined"; |
|
130 return "value"; |
|
131 } |
|
132 |
|
133 bool |
|
134 js::NewPropertyDescriptorObject(JSContext *cx, Handle<PropertyDescriptor> desc, |
|
135 MutableHandleValue vp) |
|
136 { |
|
137 if (!desc.object()) { |
|
138 vp.setUndefined(); |
|
139 return true; |
|
140 } |
|
141 |
|
142 /* We have our own property, so start creating the descriptor. */ |
|
143 AutoPropDescRooter d(cx); |
|
144 |
|
145 d.initFromPropertyDescriptor(desc); |
|
146 if (!d.makeObject(cx)) |
|
147 return false; |
|
148 vp.set(d.pd()); |
|
149 return true; |
|
150 } |
|
151 |
|
152 void |
|
153 PropDesc::initFromPropertyDescriptor(Handle<PropertyDescriptor> desc) |
|
154 { |
|
155 isUndefined_ = false; |
|
156 pd_.setUndefined(); |
|
157 attrs = uint8_t(desc.attributes()); |
|
158 JS_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER))); |
|
159 if (desc.hasGetterOrSetterObject()) { |
|
160 hasGet_ = true; |
|
161 get_ = desc.hasGetterObject() && desc.getterObject() |
|
162 ? ObjectValue(*desc.getterObject()) |
|
163 : UndefinedValue(); |
|
164 hasSet_ = true; |
|
165 set_ = desc.hasSetterObject() && desc.setterObject() |
|
166 ? ObjectValue(*desc.setterObject()) |
|
167 : UndefinedValue(); |
|
168 hasValue_ = false; |
|
169 value_.setUndefined(); |
|
170 hasWritable_ = false; |
|
171 } else { |
|
172 hasGet_ = false; |
|
173 get_.setUndefined(); |
|
174 hasSet_ = false; |
|
175 set_.setUndefined(); |
|
176 hasValue_ = true; |
|
177 value_ = desc.value(); |
|
178 hasWritable_ = true; |
|
179 } |
|
180 hasEnumerable_ = true; |
|
181 hasConfigurable_ = true; |
|
182 } |
|
183 |
|
184 bool |
|
185 PropDesc::makeObject(JSContext *cx) |
|
186 { |
|
187 MOZ_ASSERT(!isUndefined()); |
|
188 |
|
189 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); |
|
190 if (!obj) |
|
191 return false; |
|
192 |
|
193 const JSAtomState &names = cx->names(); |
|
194 RootedValue configurableVal(cx, BooleanValue((attrs & JSPROP_PERMANENT) == 0)); |
|
195 RootedValue enumerableVal(cx, BooleanValue((attrs & JSPROP_ENUMERATE) != 0)); |
|
196 RootedValue writableVal(cx, BooleanValue((attrs & JSPROP_READONLY) == 0)); |
|
197 if ((hasConfigurable() && |
|
198 !JSObject::defineProperty(cx, obj, names.configurable, configurableVal)) || |
|
199 (hasEnumerable() && |
|
200 !JSObject::defineProperty(cx, obj, names.enumerable, enumerableVal)) || |
|
201 (hasGet() && |
|
202 !JSObject::defineProperty(cx, obj, names.get, getterValue())) || |
|
203 (hasSet() && |
|
204 !JSObject::defineProperty(cx, obj, names.set, setterValue())) || |
|
205 (hasValue() && |
|
206 !JSObject::defineProperty(cx, obj, names.value, value())) || |
|
207 (hasWritable() && |
|
208 !JSObject::defineProperty(cx, obj, names.writable, writableVal))) |
|
209 { |
|
210 return false; |
|
211 } |
|
212 |
|
213 pd_.setObject(*obj); |
|
214 return true; |
|
215 } |
|
216 |
|
217 bool |
|
218 js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, |
|
219 MutableHandle<PropertyDescriptor> desc) |
|
220 { |
|
221 // FIXME: Call TrapGetOwnProperty directly once ScriptedIndirectProxies is removed |
|
222 if (obj->is<ProxyObject>()) |
|
223 return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc); |
|
224 |
|
225 RootedObject pobj(cx); |
|
226 RootedShape shape(cx); |
|
227 if (!HasOwnProperty<CanGC>(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) |
|
228 return false; |
|
229 if (!shape) { |
|
230 desc.object().set(nullptr); |
|
231 return true; |
|
232 } |
|
233 |
|
234 bool doGet = true; |
|
235 if (pobj->isNative()) { |
|
236 desc.setAttributes(GetShapeAttributes(pobj, shape)); |
|
237 if (desc.hasGetterOrSetterObject()) { |
|
238 MOZ_ASSERT(desc.isShared()); |
|
239 doGet = false; |
|
240 if (desc.hasGetterObject()) |
|
241 desc.setGetterObject(shape->getterObject()); |
|
242 if (desc.hasSetterObject()) |
|
243 desc.setSetterObject(shape->setterObject()); |
|
244 } else { |
|
245 // This is either a straight-up data property or (rarely) a |
|
246 // property with a JSPropertyOp getter/setter. The latter must be |
|
247 // reported to the caller as a plain data property, so don't |
|
248 // populate desc.getter/setter, and mask away the SHARED bit. |
|
249 desc.attributesRef() &= ~JSPROP_SHARED; |
|
250 } |
|
251 } else { |
|
252 if (!JSObject::getGenericAttributes(cx, pobj, id, &desc.attributesRef())) |
|
253 return false; |
|
254 } |
|
255 |
|
256 RootedValue value(cx); |
|
257 if (doGet && !JSObject::getGeneric(cx, obj, obj, id, &value)) |
|
258 return false; |
|
259 |
|
260 desc.value().set(value); |
|
261 desc.object().set(obj); |
|
262 return true; |
|
263 } |
|
264 |
|
265 bool |
|
266 js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) |
|
267 { |
|
268 Rooted<PropertyDescriptor> desc(cx); |
|
269 return GetOwnPropertyDescriptor(cx, obj, id, &desc) && |
|
270 NewPropertyDescriptorObject(cx, desc, vp); |
|
271 } |
|
272 |
|
273 bool |
|
274 js::GetFirstArgumentAsObject(JSContext *cx, const CallArgs &args, const char *method, |
|
275 MutableHandleObject objp) |
|
276 { |
|
277 if (args.length() == 0) { |
|
278 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
279 method, "0", "s"); |
|
280 return false; |
|
281 } |
|
282 |
|
283 HandleValue v = args[0]; |
|
284 if (!v.isObject()) { |
|
285 char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); |
|
286 if (!bytes) |
|
287 return false; |
|
288 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
289 bytes, "not an object"); |
|
290 js_free(bytes); |
|
291 return false; |
|
292 } |
|
293 |
|
294 objp.set(&v.toObject()); |
|
295 return true; |
|
296 } |
|
297 |
|
298 static bool |
|
299 HasProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, bool *foundp) |
|
300 { |
|
301 if (!JSObject::hasProperty(cx, obj, id, foundp)) |
|
302 return false; |
|
303 if (!*foundp) { |
|
304 vp.setUndefined(); |
|
305 return true; |
|
306 } |
|
307 |
|
308 /* |
|
309 * We must go through the method read barrier in case id is 'get' or 'set'. |
|
310 * There is no obvious way to defer cloning a joined function object whose |
|
311 * identity will be used by DefinePropertyOnObject, e.g., or reflected via |
|
312 * js::GetOwnPropertyDescriptor, as the getter or setter callable object. |
|
313 */ |
|
314 return !!JSObject::getGeneric(cx, obj, obj, id, vp); |
|
315 } |
|
316 |
|
317 bool |
|
318 PropDesc::initialize(JSContext *cx, const Value &origval, bool checkAccessors) |
|
319 { |
|
320 RootedValue v(cx, origval); |
|
321 |
|
322 /* 8.10.5 step 1 */ |
|
323 if (v.isPrimitive()) { |
|
324 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); |
|
325 return false; |
|
326 } |
|
327 RootedObject desc(cx, &v.toObject()); |
|
328 |
|
329 /* Make a copy of the descriptor. We might need it later. */ |
|
330 pd_ = v; |
|
331 |
|
332 isUndefined_ = false; |
|
333 |
|
334 /* |
|
335 * Start with the proper defaults. XXX shouldn't be necessary when we get |
|
336 * rid of PropDesc::attributes() |
|
337 */ |
|
338 attrs = JSPROP_PERMANENT | JSPROP_READONLY; |
|
339 |
|
340 bool found = false; |
|
341 RootedId id(cx); |
|
342 |
|
343 /* 8.10.5 step 3 */ |
|
344 id = NameToId(cx->names().enumerable); |
|
345 if (!HasProperty(cx, desc, id, &v, &found)) |
|
346 return false; |
|
347 if (found) { |
|
348 hasEnumerable_ = true; |
|
349 if (ToBoolean(v)) |
|
350 attrs |= JSPROP_ENUMERATE; |
|
351 } |
|
352 |
|
353 /* 8.10.5 step 4 */ |
|
354 id = NameToId(cx->names().configurable); |
|
355 if (!HasProperty(cx, desc, id, &v, &found)) |
|
356 return false; |
|
357 if (found) { |
|
358 hasConfigurable_ = true; |
|
359 if (ToBoolean(v)) |
|
360 attrs &= ~JSPROP_PERMANENT; |
|
361 } |
|
362 |
|
363 /* 8.10.5 step 5 */ |
|
364 id = NameToId(cx->names().value); |
|
365 if (!HasProperty(cx, desc, id, &v, &found)) |
|
366 return false; |
|
367 if (found) { |
|
368 hasValue_ = true; |
|
369 value_ = v; |
|
370 } |
|
371 |
|
372 /* 8.10.6 step 6 */ |
|
373 id = NameToId(cx->names().writable); |
|
374 if (!HasProperty(cx, desc, id, &v, &found)) |
|
375 return false; |
|
376 if (found) { |
|
377 hasWritable_ = true; |
|
378 if (ToBoolean(v)) |
|
379 attrs &= ~JSPROP_READONLY; |
|
380 } |
|
381 |
|
382 /* 8.10.7 step 7 */ |
|
383 id = NameToId(cx->names().get); |
|
384 if (!HasProperty(cx, desc, id, &v, &found)) |
|
385 return false; |
|
386 if (found) { |
|
387 hasGet_ = true; |
|
388 get_ = v; |
|
389 attrs |= JSPROP_GETTER | JSPROP_SHARED; |
|
390 attrs &= ~JSPROP_READONLY; |
|
391 if (checkAccessors && !checkGetter(cx)) |
|
392 return false; |
|
393 } |
|
394 |
|
395 /* 8.10.7 step 8 */ |
|
396 id = NameToId(cx->names().set); |
|
397 if (!HasProperty(cx, desc, id, &v, &found)) |
|
398 return false; |
|
399 if (found) { |
|
400 hasSet_ = true; |
|
401 set_ = v; |
|
402 attrs |= JSPROP_SETTER | JSPROP_SHARED; |
|
403 attrs &= ~JSPROP_READONLY; |
|
404 if (checkAccessors && !checkSetter(cx)) |
|
405 return false; |
|
406 } |
|
407 |
|
408 /* 8.10.7 step 9 */ |
|
409 if ((hasGet() || hasSet()) && (hasValue() || hasWritable())) { |
|
410 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_DESCRIPTOR); |
|
411 return false; |
|
412 } |
|
413 |
|
414 JS_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER))); |
|
415 |
|
416 return true; |
|
417 } |
|
418 |
|
419 void |
|
420 PropDesc::complete() |
|
421 { |
|
422 if (isGenericDescriptor() || isDataDescriptor()) { |
|
423 if (!hasValue_) { |
|
424 hasValue_ = true; |
|
425 value_.setUndefined(); |
|
426 } |
|
427 if (!hasWritable_) { |
|
428 hasWritable_ = true; |
|
429 attrs |= JSPROP_READONLY; |
|
430 } |
|
431 } else { |
|
432 if (!hasGet_) { |
|
433 hasGet_ = true; |
|
434 get_.setUndefined(); |
|
435 } |
|
436 if (!hasSet_) { |
|
437 hasSet_ = true; |
|
438 set_.setUndefined(); |
|
439 } |
|
440 } |
|
441 if (!hasEnumerable_) { |
|
442 hasEnumerable_ = true; |
|
443 attrs &= ~JSPROP_ENUMERATE; |
|
444 } |
|
445 if (!hasConfigurable_) { |
|
446 hasConfigurable_ = true; |
|
447 attrs |= JSPROP_PERMANENT; |
|
448 } |
|
449 } |
|
450 |
|
451 bool |
|
452 js::Throw(JSContext *cx, jsid id, unsigned errorNumber) |
|
453 { |
|
454 JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 1); |
|
455 |
|
456 JSString *idstr = IdToString(cx, id); |
|
457 if (!idstr) |
|
458 return false; |
|
459 JSAutoByteString bytes(cx, idstr); |
|
460 if (!bytes) |
|
461 return false; |
|
462 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, errorNumber, bytes.ptr()); |
|
463 return false; |
|
464 } |
|
465 |
|
466 bool |
|
467 js::Throw(JSContext *cx, JSObject *obj, unsigned errorNumber) |
|
468 { |
|
469 if (js_ErrorFormatString[errorNumber].argCount == 1) { |
|
470 RootedValue val(cx, ObjectValue(*obj)); |
|
471 js_ReportValueErrorFlags(cx, JSREPORT_ERROR, errorNumber, |
|
472 JSDVG_IGNORE_STACK, val, NullPtr(), |
|
473 nullptr, nullptr); |
|
474 } else { |
|
475 JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 0); |
|
476 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, errorNumber); |
|
477 } |
|
478 return false; |
|
479 } |
|
480 |
|
481 static bool |
|
482 Reject(JSContext *cx, unsigned errorNumber, bool throwError, jsid id, bool *rval) |
|
483 { |
|
484 if (throwError) |
|
485 return Throw(cx, id, errorNumber); |
|
486 |
|
487 *rval = false; |
|
488 return true; |
|
489 } |
|
490 |
|
491 static bool |
|
492 Reject(JSContext *cx, JSObject *obj, unsigned errorNumber, bool throwError, bool *rval) |
|
493 { |
|
494 if (throwError) |
|
495 return Throw(cx, obj, errorNumber); |
|
496 |
|
497 *rval = false; |
|
498 return true; |
|
499 } |
|
500 |
|
501 static bool |
|
502 Reject(JSContext *cx, HandleId id, unsigned errorNumber, bool throwError, bool *rval) |
|
503 { |
|
504 if (throwError) |
|
505 return Throw(cx, id, errorNumber); |
|
506 |
|
507 *rval = false; |
|
508 return true; |
|
509 } |
|
510 |
|
511 // See comments on CheckDefineProperty in jsobj.h. |
|
512 // |
|
513 // DefinePropertyOnObject has its own implementation of these checks. |
|
514 // |
|
515 JS_FRIEND_API(bool) |
|
516 js::CheckDefineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue value, |
|
517 PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
|
518 { |
|
519 if (!obj->isNative()) |
|
520 return true; |
|
521 |
|
522 // ES5 8.12.9 Step 1. Even though we know obj is native, we use generic |
|
523 // APIs for shorter, more readable code. |
|
524 Rooted<PropertyDescriptor> desc(cx); |
|
525 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) |
|
526 return false; |
|
527 |
|
528 // This does not have to check obj's extensibility when !desc.obj (steps |
|
529 // 2-3) because the low-level methods JSObject::{add,put}Property check |
|
530 // for that. |
|
531 if (desc.object() && desc.isPermanent()) { |
|
532 // Steps 6-11, skipping step 10.a.ii. Prohibit redefining a permanent |
|
533 // property with different metadata, except to make a writable property |
|
534 // non-writable. |
|
535 if (getter != desc.getter() || |
|
536 setter != desc.setter() || |
|
537 (attrs != desc.attributes() && attrs != (desc.attributes() | JSPROP_READONLY))) |
|
538 { |
|
539 return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); |
|
540 } |
|
541 |
|
542 // Step 10.a.ii. Prohibit changing the value of a non-configurable, |
|
543 // non-writable data property. |
|
544 if ((desc.attributes() & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_READONLY)) == JSPROP_READONLY) { |
|
545 bool same; |
|
546 if (!SameValue(cx, value, desc.value(), &same)) |
|
547 return false; |
|
548 if (!same) |
|
549 return JSObject::reportReadOnly(cx, id); |
|
550 } |
|
551 } |
|
552 return true; |
|
553 } |
|
554 |
|
555 static bool |
|
556 DefinePropertyOnObject(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, |
|
557 bool throwError, bool *rval) |
|
558 { |
|
559 /* 8.12.9 step 1. */ |
|
560 RootedShape shape(cx); |
|
561 RootedObject obj2(cx); |
|
562 JS_ASSERT(!obj->getOps()->lookupGeneric); |
|
563 if (!HasOwnProperty<CanGC>(cx, nullptr, obj, id, &obj2, &shape)) |
|
564 return false; |
|
565 |
|
566 JS_ASSERT(!obj->getOps()->defineProperty); |
|
567 |
|
568 /* 8.12.9 steps 2-4. */ |
|
569 if (!shape) { |
|
570 bool extensible; |
|
571 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
572 return false; |
|
573 if (!extensible) |
|
574 return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); |
|
575 |
|
576 *rval = true; |
|
577 |
|
578 if (desc.isGenericDescriptor() || desc.isDataDescriptor()) { |
|
579 JS_ASSERT(!obj->getOps()->defineProperty); |
|
580 RootedValue v(cx, desc.hasValue() ? desc.value() : UndefinedValue()); |
|
581 return baseops::DefineGeneric(cx, obj, id, v, |
|
582 JS_PropertyStub, JS_StrictPropertyStub, |
|
583 desc.attributes()); |
|
584 } |
|
585 |
|
586 JS_ASSERT(desc.isAccessorDescriptor()); |
|
587 |
|
588 return baseops::DefineGeneric(cx, obj, id, UndefinedHandleValue, |
|
589 desc.getter(), desc.setter(), desc.attributes()); |
|
590 } |
|
591 |
|
592 /* 8.12.9 steps 5-6 (note 5 is merely a special case of 6). */ |
|
593 RootedValue v(cx); |
|
594 |
|
595 JS_ASSERT(obj == obj2); |
|
596 |
|
597 bool shapeDataDescriptor = true, |
|
598 shapeAccessorDescriptor = false, |
|
599 shapeWritable = true, |
|
600 shapeConfigurable = true, |
|
601 shapeEnumerable = true, |
|
602 shapeHasDefaultGetter = true, |
|
603 shapeHasDefaultSetter = true, |
|
604 shapeHasGetterValue = false, |
|
605 shapeHasSetterValue = false; |
|
606 uint8_t shapeAttributes = GetShapeAttributes(obj, shape); |
|
607 if (!IsImplicitDenseOrTypedArrayElement(shape)) { |
|
608 shapeDataDescriptor = shape->isDataDescriptor(); |
|
609 shapeAccessorDescriptor = shape->isAccessorDescriptor(); |
|
610 shapeWritable = shape->writable(); |
|
611 shapeConfigurable = shape->configurable(); |
|
612 shapeEnumerable = shape->enumerable(); |
|
613 shapeHasDefaultGetter = shape->hasDefaultGetter(); |
|
614 shapeHasDefaultSetter = shape->hasDefaultSetter(); |
|
615 shapeHasGetterValue = shape->hasGetterValue(); |
|
616 shapeHasSetterValue = shape->hasSetterValue(); |
|
617 shapeAttributes = shape->attributes(); |
|
618 } |
|
619 |
|
620 do { |
|
621 if (desc.isAccessorDescriptor()) { |
|
622 if (!shapeAccessorDescriptor) |
|
623 break; |
|
624 |
|
625 if (desc.hasGet()) { |
|
626 bool same; |
|
627 if (!SameValue(cx, desc.getterValue(), shape->getterOrUndefined(), &same)) |
|
628 return false; |
|
629 if (!same) |
|
630 break; |
|
631 } |
|
632 |
|
633 if (desc.hasSet()) { |
|
634 bool same; |
|
635 if (!SameValue(cx, desc.setterValue(), shape->setterOrUndefined(), &same)) |
|
636 return false; |
|
637 if (!same) |
|
638 break; |
|
639 } |
|
640 } else { |
|
641 /* |
|
642 * Determine the current value of the property once, if the current |
|
643 * value might actually need to be used or preserved later. NB: we |
|
644 * guard on whether the current property is a data descriptor to |
|
645 * avoid calling a getter; we won't need the value if it's not a |
|
646 * data descriptor. |
|
647 */ |
|
648 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
649 v = obj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); |
|
650 } else if (shape->isDataDescriptor()) { |
|
651 /* |
|
652 * We must rule out a non-configurable js::PropertyOp-guarded |
|
653 * property becoming a writable unguarded data property, since |
|
654 * such a property can have its value changed to one the getter |
|
655 * and setter preclude. |
|
656 * |
|
657 * A desc lacking writable but with value is a data descriptor |
|
658 * and we must reject it as if it had writable: true if current |
|
659 * is writable. |
|
660 */ |
|
661 if (!shape->configurable() && |
|
662 (!shape->hasDefaultGetter() || !shape->hasDefaultSetter()) && |
|
663 desc.isDataDescriptor() && |
|
664 (desc.hasWritable() ? desc.writable() : shape->writable())) |
|
665 { |
|
666 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
667 } |
|
668 |
|
669 if (!NativeGet(cx, obj, obj2, shape, &v)) |
|
670 return false; |
|
671 } |
|
672 |
|
673 if (desc.isDataDescriptor()) { |
|
674 if (!shapeDataDescriptor) |
|
675 break; |
|
676 |
|
677 bool same; |
|
678 if (desc.hasValue()) { |
|
679 if (!SameValue(cx, desc.value(), v, &same)) |
|
680 return false; |
|
681 if (!same) { |
|
682 /* |
|
683 * Insist that a non-configurable js::PropertyOp data |
|
684 * property is frozen at exactly the last-got value. |
|
685 * |
|
686 * Duplicate the first part of the big conjunction that |
|
687 * we tested above, rather than add a local bool flag. |
|
688 * Likewise, don't try to keep shape->writable() in a |
|
689 * flag we veto from true to false for non-configurable |
|
690 * PropertyOp-based data properties and test before the |
|
691 * SameValue check later on in order to re-use that "if |
|
692 * (!SameValue) Reject" logic. |
|
693 * |
|
694 * This function is large and complex enough that it |
|
695 * seems best to repeat a small bit of code and return |
|
696 * Reject(...) ASAP, instead of being clever. |
|
697 */ |
|
698 if (!shapeConfigurable && |
|
699 (!shape->hasDefaultGetter() || !shape->hasDefaultSetter())) |
|
700 { |
|
701 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
702 } |
|
703 break; |
|
704 } |
|
705 } |
|
706 if (desc.hasWritable() && desc.writable() != shapeWritable) |
|
707 break; |
|
708 } else { |
|
709 /* The only fields in desc will be handled below. */ |
|
710 JS_ASSERT(desc.isGenericDescriptor()); |
|
711 } |
|
712 } |
|
713 |
|
714 if (desc.hasConfigurable() && desc.configurable() != shapeConfigurable) |
|
715 break; |
|
716 if (desc.hasEnumerable() && desc.enumerable() != shapeEnumerable) |
|
717 break; |
|
718 |
|
719 /* The conditions imposed by step 5 or step 6 apply. */ |
|
720 *rval = true; |
|
721 return true; |
|
722 } while (0); |
|
723 |
|
724 /* 8.12.9 step 7. */ |
|
725 if (!shapeConfigurable) { |
|
726 if ((desc.hasConfigurable() && desc.configurable()) || |
|
727 (desc.hasEnumerable() && desc.enumerable() != shape->enumerable())) { |
|
728 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
729 } |
|
730 } |
|
731 |
|
732 bool callDelProperty = false; |
|
733 |
|
734 if (desc.isGenericDescriptor()) { |
|
735 /* 8.12.9 step 8, no validation required */ |
|
736 } else if (desc.isDataDescriptor() != shapeDataDescriptor) { |
|
737 /* 8.12.9 step 9. */ |
|
738 if (!shapeConfigurable) |
|
739 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
740 } else if (desc.isDataDescriptor()) { |
|
741 /* 8.12.9 step 10. */ |
|
742 JS_ASSERT(shapeDataDescriptor); |
|
743 if (!shapeConfigurable && !shape->writable()) { |
|
744 if (desc.hasWritable() && desc.writable()) |
|
745 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
746 if (desc.hasValue()) { |
|
747 bool same; |
|
748 if (!SameValue(cx, desc.value(), v, &same)) |
|
749 return false; |
|
750 if (!same) |
|
751 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
752 } |
|
753 } |
|
754 |
|
755 callDelProperty = !shapeHasDefaultGetter || !shapeHasDefaultSetter; |
|
756 } else { |
|
757 /* 8.12.9 step 11. */ |
|
758 JS_ASSERT(desc.isAccessorDescriptor() && shape->isAccessorDescriptor()); |
|
759 if (!shape->configurable()) { |
|
760 if (desc.hasSet()) { |
|
761 bool same; |
|
762 if (!SameValue(cx, desc.setterValue(), shape->setterOrUndefined(), &same)) |
|
763 return false; |
|
764 if (!same) |
|
765 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
766 } |
|
767 |
|
768 if (desc.hasGet()) { |
|
769 bool same; |
|
770 if (!SameValue(cx, desc.getterValue(), shape->getterOrUndefined(), &same)) |
|
771 return false; |
|
772 if (!same) |
|
773 return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
|
774 } |
|
775 } |
|
776 } |
|
777 |
|
778 /* 8.12.9 step 12. */ |
|
779 unsigned attrs; |
|
780 PropertyOp getter; |
|
781 StrictPropertyOp setter; |
|
782 if (desc.isGenericDescriptor()) { |
|
783 unsigned changed = 0; |
|
784 if (desc.hasConfigurable()) |
|
785 changed |= JSPROP_PERMANENT; |
|
786 if (desc.hasEnumerable()) |
|
787 changed |= JSPROP_ENUMERATE; |
|
788 |
|
789 attrs = (shapeAttributes & ~changed) | (desc.attributes() & changed); |
|
790 getter = IsImplicitDenseOrTypedArrayElement(shape) ? JS_PropertyStub : shape->getter(); |
|
791 setter = IsImplicitDenseOrTypedArrayElement(shape) ? JS_StrictPropertyStub : shape->setter(); |
|
792 } else if (desc.isDataDescriptor()) { |
|
793 unsigned unchanged = 0; |
|
794 if (!desc.hasConfigurable()) |
|
795 unchanged |= JSPROP_PERMANENT; |
|
796 if (!desc.hasEnumerable()) |
|
797 unchanged |= JSPROP_ENUMERATE; |
|
798 /* Watch out for accessor -> data transformations here. */ |
|
799 if (!desc.hasWritable() && shapeDataDescriptor) |
|
800 unchanged |= JSPROP_READONLY; |
|
801 |
|
802 if (desc.hasValue()) |
|
803 v = desc.value(); |
|
804 attrs = (desc.attributes() & ~unchanged) | (shapeAttributes & unchanged); |
|
805 getter = JS_PropertyStub; |
|
806 setter = JS_StrictPropertyStub; |
|
807 } else { |
|
808 JS_ASSERT(desc.isAccessorDescriptor()); |
|
809 |
|
810 /* 8.12.9 step 12. */ |
|
811 unsigned changed = 0; |
|
812 if (desc.hasConfigurable()) |
|
813 changed |= JSPROP_PERMANENT; |
|
814 if (desc.hasEnumerable()) |
|
815 changed |= JSPROP_ENUMERATE; |
|
816 if (desc.hasGet()) |
|
817 changed |= JSPROP_GETTER | JSPROP_SHARED | JSPROP_READONLY; |
|
818 if (desc.hasSet()) |
|
819 changed |= JSPROP_SETTER | JSPROP_SHARED | JSPROP_READONLY; |
|
820 |
|
821 attrs = (desc.attributes() & changed) | (shapeAttributes & ~changed); |
|
822 if (desc.hasGet()) { |
|
823 getter = desc.getter(); |
|
824 } else { |
|
825 getter = (shapeHasDefaultGetter && !shapeHasGetterValue) |
|
826 ? JS_PropertyStub |
|
827 : shape->getter(); |
|
828 } |
|
829 if (desc.hasSet()) { |
|
830 setter = desc.setter(); |
|
831 } else { |
|
832 setter = (shapeHasDefaultSetter && !shapeHasSetterValue) |
|
833 ? JS_StrictPropertyStub |
|
834 : shape->setter(); |
|
835 } |
|
836 } |
|
837 |
|
838 *rval = true; |
|
839 |
|
840 /* |
|
841 * Since "data" properties implemented using native C functions may rely on |
|
842 * side effects during setting, we must make them aware that they have been |
|
843 * "assigned"; deleting the property before redefining it does the trick. |
|
844 * See bug 539766, where we ran into problems when we redefined |
|
845 * arguments.length without making the property aware that its value had |
|
846 * been changed (which would have happened if we had deleted it before |
|
847 * redefining it or we had invoked its setter to change its value). |
|
848 */ |
|
849 if (callDelProperty) { |
|
850 bool succeeded; |
|
851 if (!CallJSDeletePropertyOp(cx, obj2->getClass()->delProperty, obj2, id, &succeeded)) |
|
852 return false; |
|
853 } |
|
854 |
|
855 return baseops::DefineGeneric(cx, obj, id, v, getter, setter, attrs); |
|
856 } |
|
857 |
|
858 /* ES6 20130308 draft 8.4.2.1 [[DefineOwnProperty]] */ |
|
859 static bool |
|
860 DefinePropertyOnArray(JSContext *cx, Handle<ArrayObject*> arr, HandleId id, const PropDesc &desc, |
|
861 bool throwError, bool *rval) |
|
862 { |
|
863 /* Step 2. */ |
|
864 if (id == NameToId(cx->names().length)) { |
|
865 // Canonicalize value, if necessary, before proceeding any further. It |
|
866 // would be better if this were always/only done by ArraySetLength. |
|
867 // But canonicalization may throw a RangeError (or other exception, if |
|
868 // the value is an object with user-defined conversion semantics) |
|
869 // before other attributes are checked. So as long as our internal |
|
870 // defineProperty hook doesn't match the ECMA one, this duplicate |
|
871 // checking can't be helped. |
|
872 RootedValue v(cx); |
|
873 if (desc.hasValue()) { |
|
874 uint32_t newLen; |
|
875 if (!CanonicalizeArrayLengthValue<SequentialExecution>(cx, desc.value(), &newLen)) |
|
876 return false; |
|
877 v.setNumber(newLen); |
|
878 } else { |
|
879 v.setNumber(arr->length()); |
|
880 } |
|
881 |
|
882 if (desc.hasConfigurable() && desc.configurable()) |
|
883 return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
|
884 if (desc.hasEnumerable() && desc.enumerable()) |
|
885 return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
|
886 |
|
887 if (desc.isAccessorDescriptor()) |
|
888 return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
|
889 |
|
890 unsigned attrs = arr->nativeLookup(cx, id)->attributes(); |
|
891 if (!arr->lengthIsWritable()) { |
|
892 if (desc.hasWritable() && desc.writable()) |
|
893 return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
|
894 } else { |
|
895 if (desc.hasWritable() && !desc.writable()) |
|
896 attrs = attrs | JSPROP_READONLY; |
|
897 } |
|
898 |
|
899 return ArraySetLength<SequentialExecution>(cx, arr, id, attrs, v, throwError); |
|
900 } |
|
901 |
|
902 /* Step 3. */ |
|
903 uint32_t index; |
|
904 if (js_IdIsIndex(id, &index)) { |
|
905 /* Step 3b. */ |
|
906 uint32_t oldLen = arr->length(); |
|
907 |
|
908 /* Steps 3a, 3e. */ |
|
909 if (index >= oldLen && !arr->lengthIsWritable()) |
|
910 return Reject(cx, arr, JSMSG_CANT_APPEND_TO_ARRAY, throwError, rval); |
|
911 |
|
912 /* Steps 3f-j. */ |
|
913 return DefinePropertyOnObject(cx, arr, id, desc, throwError, rval); |
|
914 } |
|
915 |
|
916 /* Step 4. */ |
|
917 return DefinePropertyOnObject(cx, arr, id, desc, throwError, rval); |
|
918 } |
|
919 |
|
920 bool |
|
921 js::DefineProperty(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, |
|
922 bool throwError, bool *rval) |
|
923 { |
|
924 if (obj->is<ArrayObject>()) { |
|
925 Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); |
|
926 return DefinePropertyOnArray(cx, arr, id, desc, throwError, rval); |
|
927 } |
|
928 |
|
929 if (obj->getOps()->lookupGeneric) { |
|
930 /* |
|
931 * FIXME: Once ScriptedIndirectProxies are removed, this code should call |
|
932 * TrapDefineOwnProperty directly |
|
933 */ |
|
934 if (obj->is<ProxyObject>()) { |
|
935 RootedValue pd(cx, desc.pd()); |
|
936 return Proxy::defineProperty(cx, obj, id, pd); |
|
937 } |
|
938 return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); |
|
939 } |
|
940 |
|
941 return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval); |
|
942 } |
|
943 |
|
944 bool |
|
945 js::DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue descriptor, |
|
946 bool *bp) |
|
947 { |
|
948 AutoPropDescArrayRooter descs(cx); |
|
949 PropDesc *desc = descs.append(); |
|
950 if (!desc || !desc->initialize(cx, descriptor)) |
|
951 return false; |
|
952 |
|
953 bool rval; |
|
954 if (!DefineProperty(cx, obj, id, *desc, true, &rval)) |
|
955 return false; |
|
956 *bp = !!rval; |
|
957 return true; |
|
958 } |
|
959 |
|
960 bool |
|
961 js::DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, |
|
962 Handle<PropertyDescriptor> descriptor, bool *bp) |
|
963 { |
|
964 AutoPropDescArrayRooter descs(cx); |
|
965 PropDesc *desc = descs.append(); |
|
966 if (!desc) |
|
967 return false; |
|
968 |
|
969 desc->initFromPropertyDescriptor(descriptor); |
|
970 |
|
971 bool rval; |
|
972 if (!DefineProperty(cx, obj, id, *desc, true, &rval)) |
|
973 return false; |
|
974 *bp = !!rval; |
|
975 return true; |
|
976 } |
|
977 |
|
978 |
|
979 bool |
|
980 js::ReadPropertyDescriptors(JSContext *cx, HandleObject props, bool checkAccessors, |
|
981 AutoIdVector *ids, AutoPropDescArrayRooter *descs) |
|
982 { |
|
983 if (!GetPropertyNames(cx, props, JSITER_OWNONLY, ids)) |
|
984 return false; |
|
985 |
|
986 RootedId id(cx); |
|
987 for (size_t i = 0, len = ids->length(); i < len; i++) { |
|
988 id = (*ids)[i]; |
|
989 PropDesc* desc = descs->append(); |
|
990 RootedValue v(cx); |
|
991 if (!desc || |
|
992 !JSObject::getGeneric(cx, props, props, id, &v) || |
|
993 !desc->initialize(cx, v, checkAccessors)) |
|
994 { |
|
995 return false; |
|
996 } |
|
997 } |
|
998 return true; |
|
999 } |
|
1000 |
|
1001 bool |
|
1002 js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) |
|
1003 { |
|
1004 AutoIdVector ids(cx); |
|
1005 AutoPropDescArrayRooter descs(cx); |
|
1006 if (!ReadPropertyDescriptors(cx, props, true, &ids, &descs)) |
|
1007 return false; |
|
1008 |
|
1009 if (obj->is<ArrayObject>()) { |
|
1010 bool dummy; |
|
1011 Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); |
|
1012 for (size_t i = 0, len = ids.length(); i < len; i++) { |
|
1013 if (!DefinePropertyOnArray(cx, arr, ids.handleAt(i), descs[i], true, &dummy)) |
|
1014 return false; |
|
1015 } |
|
1016 return true; |
|
1017 } |
|
1018 |
|
1019 if (obj->getOps()->lookupGeneric) { |
|
1020 /* |
|
1021 * FIXME: Once ScriptedIndirectProxies are removed, this code should call |
|
1022 * TrapDefineOwnProperty directly |
|
1023 */ |
|
1024 if (obj->is<ProxyObject>()) { |
|
1025 for (size_t i = 0, len = ids.length(); i < len; i++) { |
|
1026 RootedValue pd(cx, descs[i].pd()); |
|
1027 if (!Proxy::defineProperty(cx, obj, ids.handleAt(i), pd)) |
|
1028 return false; |
|
1029 } |
|
1030 return true; |
|
1031 } |
|
1032 bool dummy; |
|
1033 return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, true, &dummy); |
|
1034 } |
|
1035 |
|
1036 bool dummy; |
|
1037 for (size_t i = 0, len = ids.length(); i < len; i++) { |
|
1038 if (!DefinePropertyOnObject(cx, obj, ids.handleAt(i), descs[i], true, &dummy)) |
|
1039 return false; |
|
1040 } |
|
1041 |
|
1042 return true; |
|
1043 } |
|
1044 |
|
1045 extern bool |
|
1046 js_PopulateObject(JSContext *cx, HandleObject newborn, HandleObject props) |
|
1047 { |
|
1048 return DefineProperties(cx, newborn, props); |
|
1049 } |
|
1050 |
|
1051 js::types::TypeObject* |
|
1052 JSObject::uninlinedGetType(JSContext *cx) |
|
1053 { |
|
1054 return getType(cx); |
|
1055 } |
|
1056 |
|
1057 void |
|
1058 JSObject::uninlinedSetType(js::types::TypeObject *newType) |
|
1059 { |
|
1060 setType(newType); |
|
1061 } |
|
1062 |
|
1063 /* static */ inline unsigned |
|
1064 JSObject::getSealedOrFrozenAttributes(unsigned attrs, ImmutabilityType it) |
|
1065 { |
|
1066 /* Make all attributes permanent; if freezing, make data attributes read-only. */ |
|
1067 if (it == FREEZE && !(attrs & (JSPROP_GETTER | JSPROP_SETTER))) |
|
1068 return JSPROP_PERMANENT | JSPROP_READONLY; |
|
1069 return JSPROP_PERMANENT; |
|
1070 } |
|
1071 |
|
1072 /* static */ bool |
|
1073 JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it) |
|
1074 { |
|
1075 assertSameCompartment(cx, obj); |
|
1076 JS_ASSERT(it == SEAL || it == FREEZE); |
|
1077 |
|
1078 bool extensible; |
|
1079 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
1080 return false; |
|
1081 if (extensible && !JSObject::preventExtensions(cx, obj)) |
|
1082 return false; |
|
1083 |
|
1084 AutoIdVector props(cx); |
|
1085 if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props)) |
|
1086 return false; |
|
1087 |
|
1088 /* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */ |
|
1089 JS_ASSERT_IF(obj->isNative(), obj->getDenseCapacity() == 0); |
|
1090 |
|
1091 if (obj->isNative() && !obj->inDictionaryMode() && !obj->is<TypedArrayObject>()) { |
|
1092 /* |
|
1093 * Seal/freeze non-dictionary objects by constructing a new shape |
|
1094 * hierarchy mirroring the original one, which can be shared if many |
|
1095 * objects with the same structure are sealed/frozen. If we use the |
|
1096 * generic path below then any non-empty object will be converted to |
|
1097 * dictionary mode. |
|
1098 */ |
|
1099 RootedShape last(cx, EmptyShape::getInitialShape(cx, obj->getClass(), |
|
1100 obj->getTaggedProto(), |
|
1101 obj->getParent(), |
|
1102 obj->getMetadata(), |
|
1103 obj->numFixedSlots(), |
|
1104 obj->lastProperty()->getObjectFlags())); |
|
1105 if (!last) |
|
1106 return false; |
|
1107 |
|
1108 /* Get an in order list of the shapes in this object. */ |
|
1109 AutoShapeVector shapes(cx); |
|
1110 for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) { |
|
1111 if (!shapes.append(&r.front())) |
|
1112 return false; |
|
1113 } |
|
1114 Reverse(shapes.begin(), shapes.end()); |
|
1115 |
|
1116 for (size_t i = 0; i < shapes.length(); i++) { |
|
1117 StackShape unrootedChild(shapes[i]); |
|
1118 RootedGeneric<StackShape*> child(cx, &unrootedChild); |
|
1119 child->attrs |= getSealedOrFrozenAttributes(child->attrs, it); |
|
1120 |
|
1121 if (!JSID_IS_EMPTY(child->propid) && it == FREEZE) |
|
1122 MarkTypePropertyNonWritable(cx, obj, child->propid); |
|
1123 |
|
1124 last = cx->compartment()->propertyTree.getChild(cx, last, *child); |
|
1125 if (!last) |
|
1126 return false; |
|
1127 } |
|
1128 |
|
1129 JS_ASSERT(obj->lastProperty()->slotSpan() == last->slotSpan()); |
|
1130 JS_ALWAYS_TRUE(setLastProperty(cx, obj, last)); |
|
1131 } else { |
|
1132 RootedId id(cx); |
|
1133 for (size_t i = 0; i < props.length(); i++) { |
|
1134 id = props[i]; |
|
1135 |
|
1136 unsigned attrs; |
|
1137 if (!getGenericAttributes(cx, obj, id, &attrs)) |
|
1138 return false; |
|
1139 |
|
1140 unsigned new_attrs = getSealedOrFrozenAttributes(attrs, it); |
|
1141 |
|
1142 /* If we already have the attributes we need, skip the setAttributes call. */ |
|
1143 if ((attrs | new_attrs) == attrs) |
|
1144 continue; |
|
1145 |
|
1146 attrs |= new_attrs; |
|
1147 if (!setGenericAttributes(cx, obj, id, &attrs)) |
|
1148 return false; |
|
1149 } |
|
1150 } |
|
1151 |
|
1152 // Ordinarily ArraySetLength handles this, but we're going behind its back |
|
1153 // right now, so we must do this manually. Neither the custom property |
|
1154 // tree mutations nor the setGenericAttributes call in the above code will |
|
1155 // do this for us. |
|
1156 // |
|
1157 // ArraySetLength also implements the capacity <= length invariant for |
|
1158 // arrays with non-writable length. We don't need to do anything special |
|
1159 // for that, because capacity was zeroed out by preventExtensions. (See |
|
1160 // the assertion before the if-else above.) |
|
1161 if (it == FREEZE && obj->is<ArrayObject>()) |
|
1162 obj->getElementsHeader()->setNonwritableArrayLength(); |
|
1163 |
|
1164 return true; |
|
1165 } |
|
1166 |
|
1167 /* static */ bool |
|
1168 JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it, bool *resultp) |
|
1169 { |
|
1170 bool extensible; |
|
1171 if (!JSObject::isExtensible(cx, obj, &extensible)) |
|
1172 return false; |
|
1173 if (extensible) { |
|
1174 *resultp = false; |
|
1175 return true; |
|
1176 } |
|
1177 |
|
1178 if (obj->is<TypedArrayObject>()) { |
|
1179 if (it == SEAL) { |
|
1180 // Typed arrays are always sealed. |
|
1181 *resultp = true; |
|
1182 } else { |
|
1183 // Typed arrays cannot be frozen, but an empty typed array is |
|
1184 // trivially frozen. |
|
1185 *resultp = (obj->as<TypedArrayObject>().length() == 0); |
|
1186 } |
|
1187 return true; |
|
1188 } |
|
1189 |
|
1190 AutoIdVector props(cx); |
|
1191 if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props)) |
|
1192 return false; |
|
1193 |
|
1194 RootedId id(cx); |
|
1195 for (size_t i = 0, len = props.length(); i < len; i++) { |
|
1196 id = props[i]; |
|
1197 |
|
1198 unsigned attrs; |
|
1199 if (!getGenericAttributes(cx, obj, id, &attrs)) |
|
1200 return false; |
|
1201 |
|
1202 /* |
|
1203 * If the property is configurable, this object is neither sealed nor |
|
1204 * frozen. If the property is a writable data property, this object is |
|
1205 * not frozen. |
|
1206 */ |
|
1207 if (!(attrs & JSPROP_PERMANENT) || |
|
1208 (it == FREEZE && !(attrs & (JSPROP_READONLY | JSPROP_GETTER | JSPROP_SETTER)))) |
|
1209 { |
|
1210 *resultp = false; |
|
1211 return true; |
|
1212 } |
|
1213 } |
|
1214 |
|
1215 /* All properties checked out. This object is sealed/frozen. */ |
|
1216 *resultp = true; |
|
1217 return true; |
|
1218 } |
|
1219 |
|
1220 /* static */ |
|
1221 const char * |
|
1222 JSObject::className(JSContext *cx, HandleObject obj) |
|
1223 { |
|
1224 assertSameCompartment(cx, obj); |
|
1225 |
|
1226 if (obj->is<ProxyObject>()) |
|
1227 return Proxy::className(cx, obj); |
|
1228 |
|
1229 return obj->getClass()->name; |
|
1230 } |
|
1231 |
|
1232 /* |
|
1233 * Get the GC kind to use for scripted 'new' on the given class. |
|
1234 * FIXME bug 547327: estimate the size from the allocation site. |
|
1235 */ |
|
1236 static inline gc::AllocKind |
|
1237 NewObjectGCKind(const js::Class *clasp) |
|
1238 { |
|
1239 if (clasp == &ArrayObject::class_) |
|
1240 return gc::FINALIZE_OBJECT8; |
|
1241 if (clasp == &JSFunction::class_) |
|
1242 return gc::FINALIZE_OBJECT2; |
|
1243 return gc::FINALIZE_OBJECT4; |
|
1244 } |
|
1245 |
|
1246 static inline JSObject * |
|
1247 NewObject(ExclusiveContext *cx, types::TypeObject *type_, JSObject *parent, gc::AllocKind kind, |
|
1248 NewObjectKind newKind) |
|
1249 { |
|
1250 const Class *clasp = type_->clasp(); |
|
1251 |
|
1252 JS_ASSERT(clasp != &ArrayObject::class_); |
|
1253 JS_ASSERT_IF(clasp == &JSFunction::class_, |
|
1254 kind == JSFunction::FinalizeKind || kind == JSFunction::ExtendedFinalizeKind); |
|
1255 JS_ASSERT_IF(parent, &parent->global() == cx->global()); |
|
1256 |
|
1257 RootedTypeObject type(cx, type_); |
|
1258 |
|
1259 JSObject *metadata = nullptr; |
|
1260 if (!NewObjectMetadata(cx, &metadata)) |
|
1261 return nullptr; |
|
1262 |
|
1263 // For objects which can have fixed data following the object, only use |
|
1264 // enough fixed slots to cover the number of reserved slots in the object, |
|
1265 // regardless of the allocation kind specified. |
|
1266 size_t nfixed = ClassCanHaveFixedData(clasp) |
|
1267 ? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp) |
|
1268 : GetGCKindSlots(kind, clasp); |
|
1269 |
|
1270 RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, type->proto(), |
|
1271 parent, metadata, nfixed)); |
|
1272 if (!shape) |
|
1273 return nullptr; |
|
1274 |
|
1275 gc::InitialHeap heap = GetInitialHeap(newKind, clasp); |
|
1276 JSObject *obj = JSObject::create(cx, kind, heap, shape, type); |
|
1277 if (!obj) |
|
1278 return nullptr; |
|
1279 |
|
1280 if (newKind == SingletonObject) { |
|
1281 RootedObject nobj(cx, obj); |
|
1282 if (!JSObject::setSingletonType(cx, nobj)) |
|
1283 return nullptr; |
|
1284 obj = nobj; |
|
1285 } |
|
1286 |
|
1287 /* |
|
1288 * This will cancel an already-running incremental GC from doing any more |
|
1289 * slices, and it will prevent any future incremental GCs. |
|
1290 */ |
|
1291 bool globalWithoutCustomTrace = clasp->trace == JS_GlobalObjectTraceHook && |
|
1292 !cx->compartment()->options().getTrace(); |
|
1293 if (clasp->trace && |
|
1294 !globalWithoutCustomTrace && |
|
1295 !(clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS)) |
|
1296 { |
|
1297 if (!cx->shouldBeJSContext()) |
|
1298 return nullptr; |
|
1299 JSRuntime *rt = cx->asJSContext()->runtime(); |
|
1300 rt->gcIncrementalEnabled = false; |
|
1301 |
|
1302 #ifdef DEBUG |
|
1303 if (rt->gcMode() == JSGC_MODE_INCREMENTAL) { |
|
1304 fprintf(stderr, |
|
1305 "The class %s has a trace hook but does not declare the\n" |
|
1306 "JSCLASS_IMPLEMENTS_BARRIERS flag. Please ensure that it correctly\n" |
|
1307 "implements write barriers and then set the flag.\n", |
|
1308 clasp->name); |
|
1309 MOZ_CRASH(); |
|
1310 } |
|
1311 #endif |
|
1312 } |
|
1313 |
|
1314 probes::CreateObject(cx, obj); |
|
1315 return obj; |
|
1316 } |
|
1317 |
|
1318 void |
|
1319 NewObjectCache::fillProto(EntryIndex entry, const Class *clasp, js::TaggedProto proto, |
|
1320 gc::AllocKind kind, JSObject *obj) |
|
1321 { |
|
1322 JS_ASSERT_IF(proto.isObject(), !proto.toObject()->is<GlobalObject>()); |
|
1323 JS_ASSERT(obj->getTaggedProto() == proto); |
|
1324 return fill(entry, clasp, proto.raw(), kind, obj); |
|
1325 } |
|
1326 |
|
1327 JSObject * |
|
1328 js::NewObjectWithGivenProto(ExclusiveContext *cxArg, const js::Class *clasp, |
|
1329 js::TaggedProto protoArg, JSObject *parentArg, |
|
1330 gc::AllocKind allocKind, NewObjectKind newKind) |
|
1331 { |
|
1332 if (CanBeFinalizedInBackground(allocKind, clasp)) |
|
1333 allocKind = GetBackgroundAllocKind(allocKind); |
|
1334 |
|
1335 NewObjectCache::EntryIndex entry = -1; |
|
1336 if (JSContext *cx = cxArg->maybeJSContext()) { |
|
1337 NewObjectCache &cache = cx->runtime()->newObjectCache; |
|
1338 if (protoArg.isObject() && |
|
1339 newKind == GenericObject && |
|
1340 !cx->compartment()->hasObjectMetadataCallback() && |
|
1341 (!parentArg || parentArg == protoArg.toObject()->getParent()) && |
|
1342 !protoArg.toObject()->is<GlobalObject>()) |
|
1343 { |
|
1344 if (cache.lookupProto(clasp, protoArg.toObject(), allocKind, &entry)) { |
|
1345 JSObject *obj = cache.newObjectFromHit<NoGC>(cx, entry, GetInitialHeap(newKind, clasp)); |
|
1346 if (obj) { |
|
1347 return obj; |
|
1348 } else { |
|
1349 Rooted<TaggedProto> proto(cxArg, protoArg); |
|
1350 RootedObject parent(cxArg, parentArg); |
|
1351 obj = cache.newObjectFromHit<CanGC>(cx, entry, GetInitialHeap(newKind, clasp)); |
|
1352 JS_ASSERT(!obj); |
|
1353 parentArg = parent; |
|
1354 protoArg = proto; |
|
1355 } |
|
1356 } |
|
1357 } |
|
1358 } |
|
1359 |
|
1360 Rooted<TaggedProto> proto(cxArg, protoArg); |
|
1361 RootedObject parent(cxArg, parentArg); |
|
1362 |
|
1363 types::TypeObject *type = cxArg->getNewType(clasp, proto, nullptr); |
|
1364 if (!type) |
|
1365 return nullptr; |
|
1366 |
|
1367 /* |
|
1368 * Default parent to the parent of the prototype, which was set from |
|
1369 * the parent of the prototype's constructor. |
|
1370 */ |
|
1371 if (!parent && proto.isObject()) |
|
1372 parent = proto.toObject()->getParent(); |
|
1373 |
|
1374 RootedObject obj(cxArg, NewObject(cxArg, type, parent, allocKind, newKind)); |
|
1375 if (!obj) |
|
1376 return nullptr; |
|
1377 |
|
1378 if (entry != -1 && !obj->hasDynamicSlots()) { |
|
1379 cxArg->asJSContext()->runtime()->newObjectCache.fillProto(entry, clasp, |
|
1380 proto, allocKind, obj); |
|
1381 } |
|
1382 |
|
1383 return obj; |
|
1384 } |
|
1385 |
|
1386 JSObject * |
|
1387 js::NewObjectWithClassProtoCommon(ExclusiveContext *cxArg, |
|
1388 const js::Class *clasp, JSObject *protoArg, JSObject *parentArg, |
|
1389 gc::AllocKind allocKind, NewObjectKind newKind) |
|
1390 { |
|
1391 if (protoArg) |
|
1392 return NewObjectWithGivenProto(cxArg, clasp, protoArg, parentArg, allocKind, newKind); |
|
1393 |
|
1394 if (CanBeFinalizedInBackground(allocKind, clasp)) |
|
1395 allocKind = GetBackgroundAllocKind(allocKind); |
|
1396 |
|
1397 if (!parentArg) |
|
1398 parentArg = cxArg->global(); |
|
1399 |
|
1400 /* |
|
1401 * Use the object cache, except for classes without a cached proto key. |
|
1402 * On these objects, FindProto will do a dynamic property lookup to get |
|
1403 * global[className].prototype, where changes to either the className or |
|
1404 * prototype property would render the cached lookup incorrect. For classes |
|
1405 * with a proto key, the prototype created during class initialization is |
|
1406 * stored in an immutable slot on the global (except for ClearScope, which |
|
1407 * will flush the new object cache). |
|
1408 */ |
|
1409 JSProtoKey protoKey = GetClassProtoKey(clasp); |
|
1410 |
|
1411 NewObjectCache::EntryIndex entry = -1; |
|
1412 if (JSContext *cx = cxArg->maybeJSContext()) { |
|
1413 NewObjectCache &cache = cx->runtime()->newObjectCache; |
|
1414 if (parentArg->is<GlobalObject>() && |
|
1415 protoKey != JSProto_Null && |
|
1416 newKind == GenericObject && |
|
1417 !cx->compartment()->hasObjectMetadataCallback()) |
|
1418 { |
|
1419 if (cache.lookupGlobal(clasp, &parentArg->as<GlobalObject>(), allocKind, &entry)) { |
|
1420 JSObject *obj = cache.newObjectFromHit<NoGC>(cx, entry, GetInitialHeap(newKind, clasp)); |
|
1421 if (obj) { |
|
1422 return obj; |
|
1423 } else { |
|
1424 RootedObject parent(cxArg, parentArg); |
|
1425 RootedObject proto(cxArg, protoArg); |
|
1426 obj = cache.newObjectFromHit<CanGC>(cx, entry, GetInitialHeap(newKind, clasp)); |
|
1427 JS_ASSERT(!obj); |
|
1428 protoArg = proto; |
|
1429 parentArg = parent; |
|
1430 } |
|
1431 } |
|
1432 } |
|
1433 } |
|
1434 |
|
1435 RootedObject parent(cxArg, parentArg); |
|
1436 RootedObject proto(cxArg, protoArg); |
|
1437 |
|
1438 if (!FindProto(cxArg, clasp, &proto)) |
|
1439 return nullptr; |
|
1440 |
|
1441 types::TypeObject *type = cxArg->getNewType(clasp, proto.get()); |
|
1442 if (!type) |
|
1443 return nullptr; |
|
1444 |
|
1445 JSObject *obj = NewObject(cxArg, type, parent, allocKind, newKind); |
|
1446 if (!obj) |
|
1447 return nullptr; |
|
1448 |
|
1449 if (entry != -1 && !obj->hasDynamicSlots()) { |
|
1450 cxArg->asJSContext()->runtime()->newObjectCache.fillGlobal(entry, clasp, |
|
1451 &parent->as<GlobalObject>(), |
|
1452 allocKind, obj); |
|
1453 } |
|
1454 |
|
1455 return obj; |
|
1456 } |
|
1457 |
|
1458 /* |
|
1459 * Create a plain object with the specified type. This bypasses getNewType to |
|
1460 * avoid losing creation site information for objects made by scripted 'new'. |
|
1461 */ |
|
1462 JSObject * |
|
1463 js::NewObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, gc::AllocKind allocKind, |
|
1464 NewObjectKind newKind) |
|
1465 { |
|
1466 JS_ASSERT(parent); |
|
1467 |
|
1468 JS_ASSERT(allocKind <= gc::FINALIZE_OBJECT_LAST); |
|
1469 if (CanBeFinalizedInBackground(allocKind, type->clasp())) |
|
1470 allocKind = GetBackgroundAllocKind(allocKind); |
|
1471 |
|
1472 NewObjectCache &cache = cx->runtime()->newObjectCache; |
|
1473 |
|
1474 NewObjectCache::EntryIndex entry = -1; |
|
1475 if (parent == type->proto().toObject()->getParent() && |
|
1476 newKind == GenericObject && |
|
1477 !cx->compartment()->hasObjectMetadataCallback()) |
|
1478 { |
|
1479 if (cache.lookupType(type, allocKind, &entry)) { |
|
1480 JSObject *obj = cache.newObjectFromHit<NoGC>(cx, entry, GetInitialHeap(newKind, type->clasp())); |
|
1481 if (obj) { |
|
1482 return obj; |
|
1483 } else { |
|
1484 obj = cache.newObjectFromHit<CanGC>(cx, entry, GetInitialHeap(newKind, type->clasp())); |
|
1485 parent = type->proto().toObject()->getParent(); |
|
1486 } |
|
1487 } |
|
1488 } |
|
1489 |
|
1490 JSObject *obj = NewObject(cx, type, parent, allocKind, newKind); |
|
1491 if (!obj) |
|
1492 return nullptr; |
|
1493 |
|
1494 if (entry != -1 && !obj->hasDynamicSlots()) |
|
1495 cache.fillType(entry, type, allocKind, obj); |
|
1496 |
|
1497 return obj; |
|
1498 } |
|
1499 |
|
1500 bool |
|
1501 js::NewObjectScriptedCall(JSContext *cx, MutableHandleObject pobj) |
|
1502 { |
|
1503 jsbytecode *pc; |
|
1504 RootedScript script(cx, cx->currentScript(&pc)); |
|
1505 gc::AllocKind allocKind = NewObjectGCKind(&JSObject::class_); |
|
1506 NewObjectKind newKind = script |
|
1507 ? UseNewTypeForInitializer(script, pc, &JSObject::class_) |
|
1508 : GenericObject; |
|
1509 RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind, newKind)); |
|
1510 if (!obj) |
|
1511 return false; |
|
1512 |
|
1513 if (script) { |
|
1514 /* Try to specialize the type of the object to the scripted call site. */ |
|
1515 if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) |
|
1516 return false; |
|
1517 } |
|
1518 |
|
1519 pobj.set(obj); |
|
1520 return true; |
|
1521 } |
|
1522 |
|
1523 JSObject* |
|
1524 js::CreateThis(JSContext *cx, const Class *newclasp, HandleObject callee) |
|
1525 { |
|
1526 RootedValue protov(cx); |
|
1527 if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov)) |
|
1528 return nullptr; |
|
1529 |
|
1530 JSObject *proto = protov.isObjectOrNull() ? protov.toObjectOrNull() : nullptr; |
|
1531 JSObject *parent = callee->getParent(); |
|
1532 gc::AllocKind kind = NewObjectGCKind(newclasp); |
|
1533 return NewObjectWithClassProto(cx, newclasp, proto, parent, kind); |
|
1534 } |
|
1535 |
|
1536 static inline JSObject * |
|
1537 CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, |
|
1538 NewObjectKind newKind) |
|
1539 { |
|
1540 if (type->hasNewScript()) { |
|
1541 /* |
|
1542 * Make an object with the type's associated finalize kind and shape, |
|
1543 * which reflects any properties that will definitely be added to the |
|
1544 * object before it is read from. |
|
1545 */ |
|
1546 RootedObject templateObject(cx, type->newScript()->templateObject); |
|
1547 JS_ASSERT(templateObject->type() == type); |
|
1548 |
|
1549 RootedObject res(cx, CopyInitializerObject(cx, templateObject, newKind)); |
|
1550 if (!res) |
|
1551 return nullptr; |
|
1552 if (newKind == SingletonObject) { |
|
1553 Rooted<TaggedProto> proto(cx, templateObject->getProto()); |
|
1554 if (!res->splicePrototype(cx, &JSObject::class_, proto)) |
|
1555 return nullptr; |
|
1556 } else { |
|
1557 res->setType(type); |
|
1558 } |
|
1559 return res; |
|
1560 } |
|
1561 |
|
1562 gc::AllocKind allocKind = NewObjectGCKind(&JSObject::class_); |
|
1563 return NewObjectWithType(cx, type, parent, allocKind, newKind); |
|
1564 } |
|
1565 |
|
1566 JSObject * |
|
1567 js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject *proto, |
|
1568 NewObjectKind newKind /* = GenericObject */) |
|
1569 { |
|
1570 RootedObject res(cx); |
|
1571 |
|
1572 if (proto) { |
|
1573 RootedTypeObject type(cx, cx->getNewType(&JSObject::class_, proto, &callee->as<JSFunction>())); |
|
1574 if (!type) |
|
1575 return nullptr; |
|
1576 res = CreateThisForFunctionWithType(cx, type, callee->getParent(), newKind); |
|
1577 } else { |
|
1578 gc::AllocKind allocKind = NewObjectGCKind(&JSObject::class_); |
|
1579 res = NewObjectWithClassProto(cx, &JSObject::class_, proto, callee->getParent(), allocKind, newKind); |
|
1580 } |
|
1581 |
|
1582 if (res) { |
|
1583 JSScript *script = callee->as<JSFunction>().getOrCreateScript(cx); |
|
1584 if (!script) |
|
1585 return nullptr; |
|
1586 TypeScript::SetThis(cx, script, types::Type::ObjectType(res)); |
|
1587 } |
|
1588 |
|
1589 return res; |
|
1590 } |
|
1591 |
|
1592 JSObject * |
|
1593 js::CreateThisForFunction(JSContext *cx, HandleObject callee, NewObjectKind newKind) |
|
1594 { |
|
1595 RootedValue protov(cx); |
|
1596 if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov)) |
|
1597 return nullptr; |
|
1598 JSObject *proto; |
|
1599 if (protov.isObject()) |
|
1600 proto = &protov.toObject(); |
|
1601 else |
|
1602 proto = nullptr; |
|
1603 JSObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); |
|
1604 |
|
1605 if (obj && newKind == SingletonObject) { |
|
1606 RootedObject nobj(cx, obj); |
|
1607 |
|
1608 /* Reshape the singleton before passing it as the 'this' value. */ |
|
1609 JSObject::clear(cx, nobj); |
|
1610 |
|
1611 JSScript *calleeScript = callee->as<JSFunction>().nonLazyScript(); |
|
1612 TypeScript::SetThis(cx, calleeScript, types::Type::ObjectType(nobj)); |
|
1613 |
|
1614 return nobj; |
|
1615 } |
|
1616 |
|
1617 return obj; |
|
1618 } |
|
1619 |
|
1620 /* |
|
1621 * Given pc pointing after a property accessing bytecode, return true if the |
|
1622 * access is "object-detecting" in the sense used by web scripts, e.g., when |
|
1623 * checking whether document.all is defined. |
|
1624 */ |
|
1625 static bool |
|
1626 Detecting(JSContext *cx, JSScript *script, jsbytecode *pc) |
|
1627 { |
|
1628 JS_ASSERT(script->containsPC(pc)); |
|
1629 |
|
1630 /* General case: a branch or equality op follows the access. */ |
|
1631 JSOp op = JSOp(*pc); |
|
1632 if (js_CodeSpec[op].format & JOF_DETECTING) |
|
1633 return true; |
|
1634 |
|
1635 jsbytecode *endpc = script->codeEnd(); |
|
1636 |
|
1637 if (op == JSOP_NULL) { |
|
1638 /* |
|
1639 * Special case #1: handle (document.all == null). Don't sweat |
|
1640 * about JS1.2's revision of the equality operators here. |
|
1641 */ |
|
1642 if (++pc < endpc) { |
|
1643 op = JSOp(*pc); |
|
1644 return op == JSOP_EQ || op == JSOP_NE; |
|
1645 } |
|
1646 return false; |
|
1647 } |
|
1648 |
|
1649 if (op == JSOP_GETGNAME || op == JSOP_NAME) { |
|
1650 /* |
|
1651 * Special case #2: handle (document.all == undefined). Don't worry |
|
1652 * about a local variable named |undefined| shadowing the immutable |
|
1653 * global binding...because, really? |
|
1654 */ |
|
1655 JSAtom *atom = script->getAtom(GET_UINT32_INDEX(pc)); |
|
1656 if (atom == cx->names().undefined && |
|
1657 (pc += js_CodeSpec[op].length) < endpc) { |
|
1658 op = JSOp(*pc); |
|
1659 return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE; |
|
1660 } |
|
1661 } |
|
1662 |
|
1663 return false; |
|
1664 } |
|
1665 |
|
1666 /* static */ bool |
|
1667 JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, |
|
1668 HandleId id, MutableHandleValue vp, bool strict) |
|
1669 { |
|
1670 if (MOZ_UNLIKELY(obj->watched())) { |
|
1671 WatchpointMap *wpmap = cx->compartment()->watchpointMap; |
|
1672 if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) |
|
1673 return false; |
|
1674 } |
|
1675 return obj->getOps()->setGeneric(cx, obj, id, vp, strict); |
|
1676 } |
|
1677 |
|
1678 /* static */ bool |
|
1679 JSObject::nonNativeSetElement(JSContext *cx, HandleObject obj, |
|
1680 uint32_t index, MutableHandleValue vp, bool strict) |
|
1681 { |
|
1682 if (MOZ_UNLIKELY(obj->watched())) { |
|
1683 RootedId id(cx); |
|
1684 if (!IndexToId(cx, index, &id)) |
|
1685 return false; |
|
1686 |
|
1687 WatchpointMap *wpmap = cx->compartment()->watchpointMap; |
|
1688 if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) |
|
1689 return false; |
|
1690 } |
|
1691 return obj->getOps()->setElement(cx, obj, index, vp, strict); |
|
1692 } |
|
1693 |
|
1694 /* static */ bool |
|
1695 JSObject::deleteByValue(JSContext *cx, HandleObject obj, const Value &property, bool *succeeded) |
|
1696 { |
|
1697 uint32_t index; |
|
1698 if (IsDefinitelyIndex(property, &index)) |
|
1699 return deleteElement(cx, obj, index, succeeded); |
|
1700 |
|
1701 RootedValue propval(cx, property); |
|
1702 |
|
1703 JSAtom *name = ToAtom<CanGC>(cx, propval); |
|
1704 if (!name) |
|
1705 return false; |
|
1706 |
|
1707 if (name->isIndex(&index)) |
|
1708 return deleteElement(cx, obj, index, succeeded); |
|
1709 |
|
1710 Rooted<PropertyName*> propname(cx, name->asPropertyName()); |
|
1711 return deleteProperty(cx, obj, propname, succeeded); |
|
1712 } |
|
1713 |
|
1714 JS_FRIEND_API(bool) |
|
1715 JS_CopyPropertyFrom(JSContext *cx, HandleId id, HandleObject target, |
|
1716 HandleObject obj) |
|
1717 { |
|
1718 // |obj| and |cx| are generally not same-compartment with |target| here. |
|
1719 assertSameCompartment(cx, obj, id); |
|
1720 Rooted<JSPropertyDescriptor> desc(cx); |
|
1721 |
|
1722 if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) |
|
1723 return false; |
|
1724 MOZ_ASSERT(desc.object()); |
|
1725 |
|
1726 // Silently skip JSPropertyOp-implemented accessors. |
|
1727 if (desc.getter() && !desc.hasGetterObject()) |
|
1728 return true; |
|
1729 if (desc.setter() && !desc.hasSetterObject()) |
|
1730 return true; |
|
1731 |
|
1732 JSAutoCompartment ac(cx, target); |
|
1733 RootedId wrappedId(cx, id); |
|
1734 if (!cx->compartment()->wrap(cx, &desc)) |
|
1735 return false; |
|
1736 if (!cx->compartment()->wrapId(cx, wrappedId.address())) |
|
1737 return false; |
|
1738 |
|
1739 bool ignored; |
|
1740 return DefineOwnProperty(cx, target, wrappedId, desc, &ignored); |
|
1741 } |
|
1742 |
|
1743 JS_FRIEND_API(bool) |
|
1744 JS_CopyPropertiesFrom(JSContext *cx, HandleObject target, HandleObject obj) |
|
1745 { |
|
1746 JSAutoCompartment ac(cx, obj); |
|
1747 |
|
1748 AutoIdVector props(cx); |
|
1749 if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &props)) |
|
1750 return false; |
|
1751 |
|
1752 for (size_t i = 0; i < props.length(); ++i) { |
|
1753 if (!JS_CopyPropertyFrom(cx, props.handleAt(i), target, obj)) |
|
1754 return false; |
|
1755 } |
|
1756 |
|
1757 return true; |
|
1758 } |
|
1759 |
|
1760 static bool |
|
1761 CopySlots(JSContext *cx, HandleObject from, HandleObject to) |
|
1762 { |
|
1763 JS_ASSERT(!from->isNative() && !to->isNative()); |
|
1764 JS_ASSERT(from->getClass() == to->getClass()); |
|
1765 |
|
1766 size_t n = 0; |
|
1767 if (from->is<WrapperObject>() && |
|
1768 (Wrapper::wrapperHandler(from)->flags() & |
|
1769 Wrapper::CROSS_COMPARTMENT)) { |
|
1770 to->setSlot(0, from->getSlot(0)); |
|
1771 to->setSlot(1, from->getSlot(1)); |
|
1772 n = 2; |
|
1773 } |
|
1774 |
|
1775 size_t span = JSCLASS_RESERVED_SLOTS(from->getClass()); |
|
1776 RootedValue v(cx); |
|
1777 for (; n < span; ++n) { |
|
1778 v = from->getSlot(n); |
|
1779 if (!cx->compartment()->wrap(cx, &v)) |
|
1780 return false; |
|
1781 to->setSlot(n, v); |
|
1782 } |
|
1783 return true; |
|
1784 } |
|
1785 |
|
1786 JSObject * |
|
1787 js::CloneObject(JSContext *cx, HandleObject obj, Handle<js::TaggedProto> proto, HandleObject parent) |
|
1788 { |
|
1789 if (!obj->isNative() && !obj->is<ProxyObject>()) { |
|
1790 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
1791 JSMSG_CANT_CLONE_OBJECT); |
|
1792 return nullptr; |
|
1793 } |
|
1794 |
|
1795 RootedObject clone(cx, NewObjectWithGivenProto(cx, obj->getClass(), proto, parent)); |
|
1796 if (!clone) |
|
1797 return nullptr; |
|
1798 if (obj->isNative()) { |
|
1799 if (clone->is<JSFunction>() && (obj->compartment() != clone->compartment())) { |
|
1800 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
1801 JSMSG_CANT_CLONE_OBJECT); |
|
1802 return nullptr; |
|
1803 } |
|
1804 |
|
1805 if (obj->hasPrivate()) |
|
1806 clone->setPrivate(obj->getPrivate()); |
|
1807 } else { |
|
1808 JS_ASSERT(obj->is<ProxyObject>()); |
|
1809 if (!CopySlots(cx, obj, clone)) |
|
1810 return nullptr; |
|
1811 } |
|
1812 |
|
1813 return clone; |
|
1814 } |
|
1815 |
|
1816 JSObject * |
|
1817 js::DeepCloneObjectLiteral(JSContext *cx, HandleObject obj, NewObjectKind newKind) |
|
1818 { |
|
1819 /* NB: Keep this in sync with XDRObjectLiteral. */ |
|
1820 JS_ASSERT(JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()); |
|
1821 JS_ASSERT(obj->is<JSObject>() || obj->is<ArrayObject>()); |
|
1822 |
|
1823 // Result of the clone function. |
|
1824 RootedObject clone(cx); |
|
1825 |
|
1826 // Temporary element/slot which would be stored in the cloned object. |
|
1827 RootedValue v(cx); |
|
1828 RootedObject deepObj(cx); |
|
1829 |
|
1830 if (obj->getClass() == &ArrayObject::class_) { |
|
1831 clone = NewDenseUnallocatedArray(cx, obj->as<ArrayObject>().length(), nullptr, newKind); |
|
1832 } else { |
|
1833 // Object literals are tenured by default as holded by the JSScript. |
|
1834 JS_ASSERT(obj->isTenured()); |
|
1835 AllocKind kind = obj->tenuredGetAllocKind(); |
|
1836 Rooted<TypeObject*> typeObj(cx, obj->getType(cx)); |
|
1837 if (!typeObj) |
|
1838 return nullptr; |
|
1839 RootedObject parent(cx, obj->getParent()); |
|
1840 clone = NewObjectWithGivenProto(cx, &JSObject::class_, typeObj->proto().toObject(), |
|
1841 parent, kind, newKind); |
|
1842 } |
|
1843 |
|
1844 // Allocate the same number of slots. |
|
1845 if (!clone || !clone->ensureElements(cx, obj->getDenseCapacity())) |
|
1846 return nullptr; |
|
1847 |
|
1848 // Copy the number of initialized elements. |
|
1849 uint32_t initialized = obj->getDenseInitializedLength(); |
|
1850 if (initialized) |
|
1851 clone->setDenseInitializedLength(initialized); |
|
1852 |
|
1853 // Recursive copy of dense element. |
|
1854 for (uint32_t i = 0; i < initialized; ++i) { |
|
1855 v = obj->getDenseElement(i); |
|
1856 if (v.isObject()) { |
|
1857 deepObj = &v.toObject(); |
|
1858 deepObj = js::DeepCloneObjectLiteral(cx, deepObj, newKind); |
|
1859 if (!deepObj) { |
|
1860 JS_ReportOutOfMemory(cx); |
|
1861 return nullptr; |
|
1862 } |
|
1863 v.setObject(*deepObj); |
|
1864 } |
|
1865 clone->initDenseElement(i, v); |
|
1866 } |
|
1867 |
|
1868 JS_ASSERT(obj->compartment() == clone->compartment()); |
|
1869 JS_ASSERT(!obj->hasPrivate()); |
|
1870 RootedShape shape(cx, obj->lastProperty()); |
|
1871 size_t span = shape->slotSpan(); |
|
1872 clone->setLastProperty(cx, clone, shape); |
|
1873 for (size_t i = 0; i < span; i++) { |
|
1874 v = obj->getSlot(i); |
|
1875 if (v.isObject()) { |
|
1876 deepObj = &v.toObject(); |
|
1877 deepObj = js::DeepCloneObjectLiteral(cx, deepObj, newKind); |
|
1878 if (!deepObj) |
|
1879 return nullptr; |
|
1880 v.setObject(*deepObj); |
|
1881 } |
|
1882 clone->setSlot(i, v); |
|
1883 } |
|
1884 |
|
1885 if (obj->getClass() == &ArrayObject::class_) |
|
1886 FixArrayType(cx, clone); |
|
1887 else |
|
1888 FixObjectType(cx, clone); |
|
1889 |
|
1890 #ifdef DEBUG |
|
1891 Rooted<TypeObject*> typeObj(cx, obj->getType(cx)); |
|
1892 Rooted<TypeObject*> cloneTypeObj(cx, clone->getType(cx)); |
|
1893 if (!typeObj || !cloneTypeObj) |
|
1894 return nullptr; |
|
1895 JS_ASSERT(typeObj == cloneTypeObj); |
|
1896 #endif |
|
1897 |
|
1898 return clone; |
|
1899 } |
|
1900 |
|
1901 template<XDRMode mode> |
|
1902 bool |
|
1903 js::XDRObjectLiteral(XDRState<mode> *xdr, MutableHandleObject obj) |
|
1904 { |
|
1905 /* NB: Keep this in sync with DeepCloneObjectLiteral. */ |
|
1906 |
|
1907 JSContext *cx = xdr->cx(); |
|
1908 JS_ASSERT_IF(mode == XDR_ENCODE, JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()); |
|
1909 |
|
1910 // Distinguish between objects and array classes. |
|
1911 uint32_t isArray = 0; |
|
1912 { |
|
1913 if (mode == XDR_ENCODE) { |
|
1914 JS_ASSERT(obj->is<JSObject>() || obj->is<ArrayObject>()); |
|
1915 isArray = obj->getClass() == &ArrayObject::class_ ? 1 : 0; |
|
1916 } |
|
1917 |
|
1918 if (!xdr->codeUint32(&isArray)) |
|
1919 return false; |
|
1920 } |
|
1921 |
|
1922 if (isArray) { |
|
1923 uint32_t length; |
|
1924 |
|
1925 if (mode == XDR_ENCODE) |
|
1926 length = obj->as<ArrayObject>().length(); |
|
1927 |
|
1928 if (!xdr->codeUint32(&length)) |
|
1929 return false; |
|
1930 |
|
1931 if (mode == XDR_DECODE) |
|
1932 obj.set(NewDenseUnallocatedArray(cx, length, NULL, js::MaybeSingletonObject)); |
|
1933 |
|
1934 } else { |
|
1935 // Code the alloc kind of the object. |
|
1936 AllocKind kind; |
|
1937 { |
|
1938 if (mode == XDR_ENCODE) { |
|
1939 JS_ASSERT(obj->getClass() == &JSObject::class_); |
|
1940 JS_ASSERT(obj->isTenured()); |
|
1941 kind = obj->tenuredGetAllocKind(); |
|
1942 } |
|
1943 |
|
1944 if (!xdr->codeEnum32(&kind)) |
|
1945 return false; |
|
1946 |
|
1947 if (mode == XDR_DECODE) |
|
1948 obj.set(NewBuiltinClassInstance(cx, &JSObject::class_, kind, js::MaybeSingletonObject)); |
|
1949 } |
|
1950 } |
|
1951 |
|
1952 { |
|
1953 uint32_t capacity; |
|
1954 if (mode == XDR_ENCODE) |
|
1955 capacity = obj->getDenseCapacity(); |
|
1956 if (!xdr->codeUint32(&capacity)) |
|
1957 return false; |
|
1958 if (mode == XDR_DECODE) { |
|
1959 if (!obj->ensureElements(cx, capacity)) { |
|
1960 JS_ReportOutOfMemory(cx); |
|
1961 return false; |
|
1962 } |
|
1963 } |
|
1964 } |
|
1965 |
|
1966 uint32_t initialized; |
|
1967 { |
|
1968 if (mode == XDR_ENCODE) |
|
1969 initialized = obj->getDenseInitializedLength(); |
|
1970 if (!xdr->codeUint32(&initialized)) |
|
1971 return false; |
|
1972 if (mode == XDR_DECODE) { |
|
1973 if (initialized) |
|
1974 obj->setDenseInitializedLength(initialized); |
|
1975 } |
|
1976 } |
|
1977 |
|
1978 RootedValue tmpValue(cx); |
|
1979 |
|
1980 // Recursively copy dense elements. |
|
1981 { |
|
1982 for (unsigned i = 0; i < initialized; i++) { |
|
1983 if (mode == XDR_ENCODE) |
|
1984 tmpValue = obj->getDenseElement(i); |
|
1985 |
|
1986 if (!xdr->codeConstValue(&tmpValue)) |
|
1987 return false; |
|
1988 |
|
1989 if (mode == XDR_DECODE) |
|
1990 obj->initDenseElement(i, tmpValue); |
|
1991 } |
|
1992 } |
|
1993 |
|
1994 JS_ASSERT(!obj->hasPrivate()); |
|
1995 RootedShape shape(cx, obj->lastProperty()); |
|
1996 |
|
1997 // Code the number of slots in the vector. |
|
1998 unsigned nslot = 0; |
|
1999 |
|
2000 // Code ids of the object in order. As opposed to DeepCloneObjectLiteral we |
|
2001 // cannot just re-use the shape of the original bytecode value and we have |
|
2002 // to write down the shape as well as the corresponding values. Ideally we |
|
2003 // would have a mechanism to serialize the shape too. |
|
2004 js::AutoIdVector ids(cx); |
|
2005 { |
|
2006 if (mode == XDR_ENCODE && !shape->isEmptyShape()) { |
|
2007 nslot = shape->slotSpan(); |
|
2008 if (!ids.reserve(nslot)) |
|
2009 return false; |
|
2010 |
|
2011 for (unsigned i = 0; i < nslot; i++) |
|
2012 ids.infallibleAppend(JSID_VOID); |
|
2013 |
|
2014 for (Shape::Range<NoGC> it(shape); !it.empty(); it.popFront()) { |
|
2015 // If we have reached the native property of the array class, we |
|
2016 // exit as the remaining would only be reserved slots. |
|
2017 if (!it.front().hasSlot()) { |
|
2018 JS_ASSERT(isArray); |
|
2019 break; |
|
2020 } |
|
2021 |
|
2022 JS_ASSERT(it.front().hasDefaultGetter()); |
|
2023 ids[it.front().slot()] = it.front().propid(); |
|
2024 } |
|
2025 } |
|
2026 |
|
2027 if (!xdr->codeUint32(&nslot)) |
|
2028 return false; |
|
2029 |
|
2030 RootedAtom atom(cx); |
|
2031 RootedId id(cx); |
|
2032 uint32_t idType = 0; |
|
2033 for (unsigned i = 0; i < nslot; i++) { |
|
2034 if (mode == XDR_ENCODE) { |
|
2035 id = ids[i]; |
|
2036 if (JSID_IS_INT(id)) |
|
2037 idType = JSID_TYPE_INT; |
|
2038 else if (JSID_IS_ATOM(id)) |
|
2039 idType = JSID_TYPE_STRING; |
|
2040 else |
|
2041 MOZ_ASSUME_UNREACHABLE("Object property is not yet supported by XDR."); |
|
2042 |
|
2043 tmpValue = obj->getSlot(i); |
|
2044 } |
|
2045 |
|
2046 if (!xdr->codeUint32(&idType)) |
|
2047 return false; |
|
2048 |
|
2049 if (idType == JSID_TYPE_STRING) { |
|
2050 if (mode == XDR_ENCODE) |
|
2051 atom = JSID_TO_ATOM(id); |
|
2052 if (!XDRAtom(xdr, &atom)) |
|
2053 return false; |
|
2054 if (mode == XDR_DECODE) |
|
2055 id = AtomToId(atom); |
|
2056 } else { |
|
2057 JS_ASSERT(idType == JSID_TYPE_INT); |
|
2058 uint32_t indexVal; |
|
2059 if (mode == XDR_ENCODE) |
|
2060 indexVal = uint32_t(JSID_TO_INT(id)); |
|
2061 if (!xdr->codeUint32(&indexVal)) |
|
2062 return false; |
|
2063 if (mode == XDR_DECODE) |
|
2064 id = INT_TO_JSID(int32_t(indexVal)); |
|
2065 } |
|
2066 |
|
2067 if (!xdr->codeConstValue(&tmpValue)) |
|
2068 return false; |
|
2069 |
|
2070 if (mode == XDR_DECODE) { |
|
2071 if (!DefineNativeProperty(cx, obj, id, tmpValue, NULL, NULL, JSPROP_ENUMERATE)) |
|
2072 return false; |
|
2073 } |
|
2074 } |
|
2075 |
|
2076 JS_ASSERT_IF(mode == XDR_DECODE, !obj->inDictionaryMode()); |
|
2077 } |
|
2078 |
|
2079 if (mode == XDR_DECODE) { |
|
2080 if (isArray) |
|
2081 FixArrayType(cx, obj); |
|
2082 else |
|
2083 FixObjectType(cx, obj); |
|
2084 } |
|
2085 |
|
2086 return true; |
|
2087 } |
|
2088 |
|
2089 template bool |
|
2090 js::XDRObjectLiteral(XDRState<XDR_ENCODE> *xdr, MutableHandleObject obj); |
|
2091 |
|
2092 template bool |
|
2093 js::XDRObjectLiteral(XDRState<XDR_DECODE> *xdr, MutableHandleObject obj); |
|
2094 |
|
2095 JSObject * |
|
2096 js::CloneObjectLiteral(JSContext *cx, HandleObject parent, HandleObject srcObj) |
|
2097 { |
|
2098 Rooted<TypeObject*> typeObj(cx); |
|
2099 typeObj = cx->getNewType(&JSObject::class_, cx->global()->getOrCreateObjectPrototype(cx)); |
|
2100 |
|
2101 JS_ASSERT(srcObj->getClass() == &JSObject::class_); |
|
2102 AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots())); |
|
2103 JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind()); |
|
2104 |
|
2105 RootedShape shape(cx, srcObj->lastProperty()); |
|
2106 return NewReshapedObject(cx, typeObj, parent, kind, shape); |
|
2107 } |
|
2108 |
|
2109 struct JSObject::TradeGutsReserved { |
|
2110 Vector<Value> avals; |
|
2111 Vector<Value> bvals; |
|
2112 int newafixed; |
|
2113 int newbfixed; |
|
2114 RootedShape newashape; |
|
2115 RootedShape newbshape; |
|
2116 HeapSlot *newaslots; |
|
2117 HeapSlot *newbslots; |
|
2118 |
|
2119 TradeGutsReserved(JSContext *cx) |
|
2120 : avals(cx), bvals(cx), |
|
2121 newafixed(0), newbfixed(0), |
|
2122 newashape(cx), newbshape(cx), |
|
2123 newaslots(nullptr), newbslots(nullptr) |
|
2124 {} |
|
2125 |
|
2126 ~TradeGutsReserved() |
|
2127 { |
|
2128 js_free(newaslots); |
|
2129 js_free(newbslots); |
|
2130 } |
|
2131 }; |
|
2132 |
|
2133 bool |
|
2134 JSObject::ReserveForTradeGuts(JSContext *cx, JSObject *aArg, JSObject *bArg, |
|
2135 TradeGutsReserved &reserved) |
|
2136 { |
|
2137 /* |
|
2138 * Avoid GC in here to avoid confusing the tracing code with our |
|
2139 * intermediate state. |
|
2140 */ |
|
2141 AutoSuppressGC suppress(cx); |
|
2142 |
|
2143 RootedObject a(cx, aArg); |
|
2144 RootedObject b(cx, bArg); |
|
2145 JS_ASSERT(a->compartment() == b->compartment()); |
|
2146 AutoCompartment ac(cx, a); |
|
2147 |
|
2148 /* |
|
2149 * When performing multiple swaps between objects which may have different |
|
2150 * numbers of fixed slots, we reserve all space ahead of time so that the |
|
2151 * swaps can be performed infallibly. |
|
2152 */ |
|
2153 |
|
2154 /* |
|
2155 * Swap prototypes and classes on the two objects, so that TradeGuts can |
|
2156 * preserve the types of the two objects. |
|
2157 */ |
|
2158 const Class *aClass = a->getClass(); |
|
2159 const Class *bClass = b->getClass(); |
|
2160 Rooted<TaggedProto> aProto(cx, a->getTaggedProto()); |
|
2161 Rooted<TaggedProto> bProto(cx, b->getTaggedProto()); |
|
2162 bool success; |
|
2163 if (!SetClassAndProto(cx, a, bClass, bProto, &success) || !success) |
|
2164 return false; |
|
2165 if (!SetClassAndProto(cx, b, aClass, aProto, &success) || !success) |
|
2166 return false; |
|
2167 |
|
2168 if (a->tenuredSizeOfThis() == b->tenuredSizeOfThis()) |
|
2169 return true; |
|
2170 |
|
2171 /* |
|
2172 * If either object is native, it needs a new shape to preserve the |
|
2173 * invariant that objects with the same shape have the same number of |
|
2174 * inline slots. The fixed slots will be updated in place during TradeGuts. |
|
2175 * Non-native objects need to be reshaped according to the new count. |
|
2176 */ |
|
2177 if (a->isNative()) { |
|
2178 if (!a->generateOwnShape(cx)) |
|
2179 return false; |
|
2180 } else { |
|
2181 reserved.newbshape = EmptyShape::getInitialShape(cx, aClass, aProto, a->getParent(), a->getMetadata(), |
|
2182 b->tenuredGetAllocKind()); |
|
2183 if (!reserved.newbshape) |
|
2184 return false; |
|
2185 } |
|
2186 if (b->isNative()) { |
|
2187 if (!b->generateOwnShape(cx)) |
|
2188 return false; |
|
2189 } else { |
|
2190 reserved.newashape = EmptyShape::getInitialShape(cx, bClass, bProto, b->getParent(), b->getMetadata(), |
|
2191 a->tenuredGetAllocKind()); |
|
2192 if (!reserved.newashape) |
|
2193 return false; |
|
2194 } |
|
2195 |
|
2196 /* The avals/bvals vectors hold all original values from the objects. */ |
|
2197 |
|
2198 if (!reserved.avals.reserve(a->slotSpan())) |
|
2199 return false; |
|
2200 if (!reserved.bvals.reserve(b->slotSpan())) |
|
2201 return false; |
|
2202 |
|
2203 /* |
|
2204 * The newafixed/newbfixed hold the number of fixed slots in the objects |
|
2205 * after the swap. Adjust these counts according to whether the objects |
|
2206 * use their last fixed slot for storing private data. |
|
2207 */ |
|
2208 |
|
2209 reserved.newafixed = a->numFixedSlots(); |
|
2210 reserved.newbfixed = b->numFixedSlots(); |
|
2211 |
|
2212 if (aClass->hasPrivate()) { |
|
2213 reserved.newafixed++; |
|
2214 reserved.newbfixed--; |
|
2215 } |
|
2216 if (bClass->hasPrivate()) { |
|
2217 reserved.newbfixed++; |
|
2218 reserved.newafixed--; |
|
2219 } |
|
2220 |
|
2221 JS_ASSERT(reserved.newafixed >= 0); |
|
2222 JS_ASSERT(reserved.newbfixed >= 0); |
|
2223 |
|
2224 /* |
|
2225 * The newaslots/newbslots arrays hold any dynamic slots for the objects |
|
2226 * if they do not have enough fixed slots to accomodate the slots in the |
|
2227 * other object. |
|
2228 */ |
|
2229 |
|
2230 unsigned adynamic = dynamicSlotsCount(reserved.newafixed, b->slotSpan(), b->getClass()); |
|
2231 unsigned bdynamic = dynamicSlotsCount(reserved.newbfixed, a->slotSpan(), a->getClass()); |
|
2232 |
|
2233 if (adynamic) { |
|
2234 reserved.newaslots = cx->pod_malloc<HeapSlot>(adynamic); |
|
2235 if (!reserved.newaslots) |
|
2236 return false; |
|
2237 Debug_SetSlotRangeToCrashOnTouch(reserved.newaslots, adynamic); |
|
2238 } |
|
2239 if (bdynamic) { |
|
2240 reserved.newbslots = cx->pod_malloc<HeapSlot>(bdynamic); |
|
2241 if (!reserved.newbslots) |
|
2242 return false; |
|
2243 Debug_SetSlotRangeToCrashOnTouch(reserved.newbslots, bdynamic); |
|
2244 } |
|
2245 |
|
2246 return true; |
|
2247 } |
|
2248 |
|
2249 void |
|
2250 JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved &reserved) |
|
2251 { |
|
2252 JS_ASSERT(a->compartment() == b->compartment()); |
|
2253 JS_ASSERT(a->is<JSFunction>() == b->is<JSFunction>()); |
|
2254 |
|
2255 /* |
|
2256 * Swap the object's types, to restore their initial type information. |
|
2257 * The prototypes and classes of the objects were swapped in ReserveForTradeGuts. |
|
2258 */ |
|
2259 TypeObject *tmp = a->type_; |
|
2260 a->type_ = b->type_; |
|
2261 b->type_ = tmp; |
|
2262 |
|
2263 /* Don't try to swap a JSFunction for a plain function JSObject. */ |
|
2264 JS_ASSERT_IF(a->is<JSFunction>(), a->tenuredSizeOfThis() == b->tenuredSizeOfThis()); |
|
2265 |
|
2266 /* |
|
2267 * Regexp guts are more complicated -- we would need to migrate the |
|
2268 * refcounted JIT code blob for them across compartments instead of just |
|
2269 * swapping guts. |
|
2270 */ |
|
2271 JS_ASSERT(!a->is<RegExpObject>() && !b->is<RegExpObject>()); |
|
2272 |
|
2273 /* Arrays can use their fixed storage for elements. */ |
|
2274 JS_ASSERT(!a->is<ArrayObject>() && !b->is<ArrayObject>()); |
|
2275 |
|
2276 /* |
|
2277 * Callers should not try to swap ArrayBuffer objects, |
|
2278 * these use a different slot representation from other objects. |
|
2279 */ |
|
2280 JS_ASSERT(!a->is<ArrayBufferObject>() && !b->is<ArrayBufferObject>()); |
|
2281 |
|
2282 /* Trade the guts of the objects. */ |
|
2283 const size_t size = a->tenuredSizeOfThis(); |
|
2284 if (size == b->tenuredSizeOfThis()) { |
|
2285 /* |
|
2286 * If the objects are the same size, then we make no assumptions about |
|
2287 * whether they have dynamically allocated slots and instead just copy |
|
2288 * them over wholesale. |
|
2289 */ |
|
2290 char tmp[mozilla::tl::Max<sizeof(JSFunction), sizeof(JSObject_Slots16)>::value]; |
|
2291 JS_ASSERT(size <= sizeof(tmp)); |
|
2292 |
|
2293 js_memcpy(tmp, a, size); |
|
2294 js_memcpy(a, b, size); |
|
2295 js_memcpy(b, tmp, size); |
|
2296 |
|
2297 #ifdef JSGC_GENERATIONAL |
|
2298 /* |
|
2299 * Trigger post barriers for fixed slots. JSObject bits are barriered |
|
2300 * below, in common with the other case. |
|
2301 */ |
|
2302 for (size_t i = 0; i < a->numFixedSlots(); ++i) { |
|
2303 HeapSlot::writeBarrierPost(cx->runtime(), a, HeapSlot::Slot, i, a->getSlot(i)); |
|
2304 HeapSlot::writeBarrierPost(cx->runtime(), b, HeapSlot::Slot, i, b->getSlot(i)); |
|
2305 } |
|
2306 #endif |
|
2307 } else { |
|
2308 /* |
|
2309 * If the objects are of differing sizes, use the space we reserved |
|
2310 * earlier to save the slots from each object and then copy them into |
|
2311 * the new layout for the other object. |
|
2312 */ |
|
2313 |
|
2314 uint32_t acap = a->slotSpan(); |
|
2315 uint32_t bcap = b->slotSpan(); |
|
2316 |
|
2317 for (size_t i = 0; i < acap; i++) |
|
2318 reserved.avals.infallibleAppend(a->getSlot(i)); |
|
2319 |
|
2320 for (size_t i = 0; i < bcap; i++) |
|
2321 reserved.bvals.infallibleAppend(b->getSlot(i)); |
|
2322 |
|
2323 /* Done with the dynamic slots. */ |
|
2324 if (a->hasDynamicSlots()) |
|
2325 js_free(a->slots); |
|
2326 if (b->hasDynamicSlots()) |
|
2327 js_free(b->slots); |
|
2328 |
|
2329 void *apriv = a->hasPrivate() ? a->getPrivate() : nullptr; |
|
2330 void *bpriv = b->hasPrivate() ? b->getPrivate() : nullptr; |
|
2331 |
|
2332 char tmp[sizeof(JSObject)]; |
|
2333 js_memcpy(&tmp, a, sizeof tmp); |
|
2334 js_memcpy(a, b, sizeof tmp); |
|
2335 js_memcpy(b, &tmp, sizeof tmp); |
|
2336 |
|
2337 if (a->isNative()) |
|
2338 a->shape_->setNumFixedSlots(reserved.newafixed); |
|
2339 else |
|
2340 a->shape_ = reserved.newashape; |
|
2341 |
|
2342 a->slots = reserved.newaslots; |
|
2343 a->initSlotRange(0, reserved.bvals.begin(), bcap); |
|
2344 if (a->hasPrivate()) |
|
2345 a->initPrivate(bpriv); |
|
2346 |
|
2347 if (b->isNative()) |
|
2348 b->shape_->setNumFixedSlots(reserved.newbfixed); |
|
2349 else |
|
2350 b->shape_ = reserved.newbshape; |
|
2351 |
|
2352 b->slots = reserved.newbslots; |
|
2353 b->initSlotRange(0, reserved.avals.begin(), acap); |
|
2354 if (b->hasPrivate()) |
|
2355 b->initPrivate(apriv); |
|
2356 |
|
2357 /* Make sure the destructor for reserved doesn't free the slots. */ |
|
2358 reserved.newaslots = nullptr; |
|
2359 reserved.newbslots = nullptr; |
|
2360 } |
|
2361 |
|
2362 #ifdef JSGC_GENERATIONAL |
|
2363 Shape::writeBarrierPost(a->shape_, &a->shape_); |
|
2364 Shape::writeBarrierPost(b->shape_, &b->shape_); |
|
2365 types::TypeObject::writeBarrierPost(a->type_, &a->type_); |
|
2366 types::TypeObject::writeBarrierPost(b->type_, &b->type_); |
|
2367 #endif |
|
2368 |
|
2369 if (a->inDictionaryMode()) |
|
2370 a->lastProperty()->listp = &a->shape_; |
|
2371 if (b->inDictionaryMode()) |
|
2372 b->lastProperty()->listp = &b->shape_; |
|
2373 |
|
2374 #ifdef JSGC_INCREMENTAL |
|
2375 /* |
|
2376 * We need a write barrier here. If |a| was marked and |b| was not, then |
|
2377 * after the swap, |b|'s guts would never be marked. The write barrier |
|
2378 * solves this. |
|
2379 * |
|
2380 * Normally write barriers happen before the write. However, that's not |
|
2381 * necessary here because nothing is being destroyed. We're just swapping. |
|
2382 * We don't do the barrier before TradeGuts because ReserveForTradeGuts |
|
2383 * makes changes to the objects that might confuse the tracing code. |
|
2384 */ |
|
2385 JS::Zone *zone = a->zone(); |
|
2386 if (zone->needsBarrier()) { |
|
2387 MarkChildren(zone->barrierTracer(), a); |
|
2388 MarkChildren(zone->barrierTracer(), b); |
|
2389 } |
|
2390 #endif |
|
2391 } |
|
2392 |
|
2393 /* Use this method with extreme caution. It trades the guts of two objects. */ |
|
2394 bool |
|
2395 JSObject::swap(JSContext *cx, HandleObject a, HandleObject b) |
|
2396 { |
|
2397 AutoMarkInDeadZone adc1(a->zone()); |
|
2398 AutoMarkInDeadZone adc2(b->zone()); |
|
2399 |
|
2400 // Ensure swap doesn't cause a finalizer to not be run. |
|
2401 JS_ASSERT(IsBackgroundFinalized(a->tenuredGetAllocKind()) == |
|
2402 IsBackgroundFinalized(b->tenuredGetAllocKind())); |
|
2403 JS_ASSERT(a->compartment() == b->compartment()); |
|
2404 |
|
2405 unsigned r = NotifyGCPreSwap(a, b); |
|
2406 |
|
2407 TradeGutsReserved reserved(cx); |
|
2408 if (!ReserveForTradeGuts(cx, a, b, reserved)) { |
|
2409 NotifyGCPostSwap(b, a, r); |
|
2410 return false; |
|
2411 } |
|
2412 TradeGuts(cx, a, b, reserved); |
|
2413 |
|
2414 NotifyGCPostSwap(a, b, r); |
|
2415 return true; |
|
2416 } |
|
2417 |
|
2418 static bool |
|
2419 DefineStandardSlot(JSContext *cx, HandleObject obj, JSProtoKey key, JSAtom *atom, |
|
2420 HandleValue v, uint32_t attrs, bool &named) |
|
2421 { |
|
2422 RootedId id(cx, AtomToId(atom)); |
|
2423 |
|
2424 if (key != JSProto_Null) { |
|
2425 /* |
|
2426 * Initializing an actual standard class on a global object. If the |
|
2427 * property is not yet present, force it into a new one bound to a |
|
2428 * reserved slot. Otherwise, go through the normal property path. |
|
2429 */ |
|
2430 JS_ASSERT(obj->is<GlobalObject>()); |
|
2431 JS_ASSERT(obj->isNative()); |
|
2432 |
|
2433 if (!obj->nativeLookup(cx, id)) { |
|
2434 obj->as<GlobalObject>().setConstructorPropertySlot(key, v); |
|
2435 |
|
2436 uint32_t slot = GlobalObject::constructorPropertySlot(key); |
|
2437 if (!JSObject::addProperty(cx, obj, id, JS_PropertyStub, JS_StrictPropertyStub, slot, attrs, 0)) |
|
2438 return false; |
|
2439 |
|
2440 named = true; |
|
2441 return true; |
|
2442 } |
|
2443 } |
|
2444 |
|
2445 named = JSObject::defineGeneric(cx, obj, id, |
|
2446 v, JS_PropertyStub, JS_StrictPropertyStub, attrs); |
|
2447 return named; |
|
2448 } |
|
2449 |
|
2450 static void |
|
2451 SetClassObject(JSObject *obj, JSProtoKey key, JSObject *cobj, JSObject *proto) |
|
2452 { |
|
2453 JS_ASSERT(!obj->getParent()); |
|
2454 if (!obj->is<GlobalObject>()) |
|
2455 return; |
|
2456 |
|
2457 obj->as<GlobalObject>().setConstructor(key, ObjectOrNullValue(cobj)); |
|
2458 obj->as<GlobalObject>().setPrototype(key, ObjectOrNullValue(proto)); |
|
2459 } |
|
2460 |
|
2461 static void |
|
2462 ClearClassObject(JSObject *obj, JSProtoKey key) |
|
2463 { |
|
2464 JS_ASSERT(!obj->getParent()); |
|
2465 if (!obj->is<GlobalObject>()) |
|
2466 return; |
|
2467 |
|
2468 obj->as<GlobalObject>().setConstructor(key, UndefinedValue()); |
|
2469 obj->as<GlobalObject>().setPrototype(key, UndefinedValue()); |
|
2470 } |
|
2471 |
|
2472 static JSObject * |
|
2473 DefineConstructorAndPrototype(JSContext *cx, HandleObject obj, JSProtoKey key, HandleAtom atom, |
|
2474 JSObject *protoProto, const Class *clasp, |
|
2475 Native constructor, unsigned nargs, |
|
2476 const JSPropertySpec *ps, const JSFunctionSpec *fs, |
|
2477 const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, |
|
2478 JSObject **ctorp, AllocKind ctorKind) |
|
2479 { |
|
2480 /* |
|
2481 * Create a prototype object for this class. |
|
2482 * |
|
2483 * FIXME: lazy standard (built-in) class initialization and even older |
|
2484 * eager boostrapping code rely on all of these properties: |
|
2485 * |
|
2486 * 1. NewObject attempting to compute a default prototype object when |
|
2487 * passed null for proto; and |
|
2488 * |
|
2489 * 2. NewObject tolerating no default prototype (null proto slot value) |
|
2490 * due to this js_InitClass call coming from js_InitFunctionClass on an |
|
2491 * otherwise-uninitialized global. |
|
2492 * |
|
2493 * 3. NewObject allocating a JSFunction-sized GC-thing when clasp is |
|
2494 * &JSFunction::class_, not a JSObject-sized (smaller) GC-thing. |
|
2495 * |
|
2496 * The JS_NewObjectForGivenProto and JS_NewObject APIs also allow clasp to |
|
2497 * be &JSFunction::class_ (we could break compatibility easily). But |
|
2498 * fixing (3) is not enough without addressing the bootstrapping dependency |
|
2499 * on (1) and (2). |
|
2500 */ |
|
2501 |
|
2502 /* |
|
2503 * Create the prototype object. (GlobalObject::createBlankPrototype isn't |
|
2504 * used because it parents the prototype object to the global and because |
|
2505 * it uses WithProto::Given. FIXME: Undo dependencies on this parentage |
|
2506 * [which already needs to happen for bug 638316], figure out nicer |
|
2507 * semantics for null-protoProto, and use createBlankPrototype.) |
|
2508 */ |
|
2509 RootedObject proto(cx, NewObjectWithClassProto(cx, clasp, protoProto, obj, SingletonObject)); |
|
2510 if (!proto) |
|
2511 return nullptr; |
|
2512 |
|
2513 /* After this point, control must exit via label bad or out. */ |
|
2514 RootedObject ctor(cx); |
|
2515 bool named = false; |
|
2516 bool cached = false; |
|
2517 if (!constructor) { |
|
2518 /* |
|
2519 * Lacking a constructor, name the prototype (e.g., Math) unless this |
|
2520 * class (a) is anonymous, i.e. for internal use only; (b) the class |
|
2521 * of obj (the global object) is has a reserved slot indexed by key; |
|
2522 * and (c) key is not the null key. |
|
2523 */ |
|
2524 if (!(clasp->flags & JSCLASS_IS_ANONYMOUS) || !obj->is<GlobalObject>() || |
|
2525 key == JSProto_Null) |
|
2526 { |
|
2527 uint32_t attrs = (clasp->flags & JSCLASS_IS_ANONYMOUS) |
|
2528 ? JSPROP_READONLY | JSPROP_PERMANENT |
|
2529 : 0; |
|
2530 RootedValue value(cx, ObjectValue(*proto)); |
|
2531 if (!DefineStandardSlot(cx, obj, key, atom, value, attrs, named)) |
|
2532 goto bad; |
|
2533 } |
|
2534 |
|
2535 ctor = proto; |
|
2536 } else { |
|
2537 /* |
|
2538 * Create the constructor, not using GlobalObject::createConstructor |
|
2539 * because the constructor currently must have |obj| as its parent. |
|
2540 * (FIXME: remove this dependency on the exact identity of the parent, |
|
2541 * perhaps as part of bug 638316.) |
|
2542 */ |
|
2543 RootedFunction fun(cx, NewFunction(cx, js::NullPtr(), constructor, nargs, |
|
2544 JSFunction::NATIVE_CTOR, obj, atom, ctorKind)); |
|
2545 if (!fun) |
|
2546 goto bad; |
|
2547 |
|
2548 /* |
|
2549 * Set the class object early for standard class constructors. Type |
|
2550 * inference may need to access these, and js::GetBuiltinPrototype will |
|
2551 * fail if it tries to do a reentrant reconstruction of the class. |
|
2552 */ |
|
2553 if (key != JSProto_Null) { |
|
2554 SetClassObject(obj, key, fun, proto); |
|
2555 cached = true; |
|
2556 } |
|
2557 |
|
2558 RootedValue value(cx, ObjectValue(*fun)); |
|
2559 if (!DefineStandardSlot(cx, obj, key, atom, value, 0, named)) |
|
2560 goto bad; |
|
2561 |
|
2562 /* |
|
2563 * Optionally construct the prototype object, before the class has |
|
2564 * been fully initialized. Allow the ctor to replace proto with a |
|
2565 * different object, as is done for operator new. |
|
2566 */ |
|
2567 ctor = fun; |
|
2568 if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
|
2569 goto bad; |
|
2570 |
|
2571 /* Bootstrap Function.prototype (see also JS_InitStandardClasses). */ |
|
2572 Rooted<TaggedProto> tagged(cx, TaggedProto(proto)); |
|
2573 if (ctor->getClass() == clasp && !ctor->splicePrototype(cx, clasp, tagged)) |
|
2574 goto bad; |
|
2575 } |
|
2576 |
|
2577 if (!DefinePropertiesAndBrand(cx, proto, ps, fs) || |
|
2578 (ctor != proto && !DefinePropertiesAndBrand(cx, ctor, static_ps, static_fs))) |
|
2579 { |
|
2580 goto bad; |
|
2581 } |
|
2582 |
|
2583 /* If this is a standard class, cache its prototype. */ |
|
2584 if (!cached && key != JSProto_Null) |
|
2585 SetClassObject(obj, key, ctor, proto); |
|
2586 |
|
2587 if (ctorp) |
|
2588 *ctorp = ctor; |
|
2589 return proto; |
|
2590 |
|
2591 bad: |
|
2592 if (named) { |
|
2593 bool succeeded; |
|
2594 JSObject::deleteByValue(cx, obj, StringValue(atom), &succeeded); |
|
2595 } |
|
2596 if (cached) |
|
2597 ClearClassObject(obj, key); |
|
2598 return nullptr; |
|
2599 } |
|
2600 |
|
2601 JSObject * |
|
2602 js_InitClass(JSContext *cx, HandleObject obj, JSObject *protoProto_, |
|
2603 const Class *clasp, Native constructor, unsigned nargs, |
|
2604 const JSPropertySpec *ps, const JSFunctionSpec *fs, |
|
2605 const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, |
|
2606 JSObject **ctorp, AllocKind ctorKind) |
|
2607 { |
|
2608 RootedObject protoProto(cx, protoProto_); |
|
2609 |
|
2610 /* Assert mandatory function pointer members. */ |
|
2611 JS_ASSERT(clasp->addProperty); |
|
2612 JS_ASSERT(clasp->delProperty); |
|
2613 JS_ASSERT(clasp->getProperty); |
|
2614 JS_ASSERT(clasp->setProperty); |
|
2615 JS_ASSERT(clasp->enumerate); |
|
2616 JS_ASSERT(clasp->resolve); |
|
2617 JS_ASSERT(clasp->convert); |
|
2618 |
|
2619 RootedAtom atom(cx, Atomize(cx, clasp->name, strlen(clasp->name))); |
|
2620 if (!atom) |
|
2621 return nullptr; |
|
2622 |
|
2623 /* |
|
2624 * All instances of the class will inherit properties from the prototype |
|
2625 * object we are about to create (in DefineConstructorAndPrototype), which |
|
2626 * in turn will inherit from protoProto. |
|
2627 * |
|
2628 * When initializing a standard class (other than Object), if protoProto is |
|
2629 * null, default to Object.prototype. The engine's internal uses of |
|
2630 * js_InitClass depend on this nicety. |
|
2631 */ |
|
2632 JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp); |
|
2633 if (key != JSProto_Null && |
|
2634 !protoProto && |
|
2635 !GetBuiltinPrototype(cx, JSProto_Object, &protoProto)) |
|
2636 { |
|
2637 return nullptr; |
|
2638 } |
|
2639 |
|
2640 return DefineConstructorAndPrototype(cx, obj, key, atom, protoProto, clasp, constructor, nargs, |
|
2641 ps, fs, static_ps, static_fs, ctorp, ctorKind); |
|
2642 } |
|
2643 |
|
2644 /* static */ inline bool |
|
2645 JSObject::updateSlotsForSpan(ThreadSafeContext *cx, |
|
2646 HandleObject obj, size_t oldSpan, size_t newSpan) |
|
2647 { |
|
2648 JS_ASSERT(cx->isThreadLocal(obj)); |
|
2649 JS_ASSERT(oldSpan != newSpan); |
|
2650 |
|
2651 size_t oldCount = dynamicSlotsCount(obj->numFixedSlots(), oldSpan, obj->getClass()); |
|
2652 size_t newCount = dynamicSlotsCount(obj->numFixedSlots(), newSpan, obj->getClass()); |
|
2653 |
|
2654 if (oldSpan < newSpan) { |
|
2655 if (oldCount < newCount && !JSObject::growSlots(cx, obj, oldCount, newCount)) |
|
2656 return false; |
|
2657 |
|
2658 if (newSpan == oldSpan + 1) |
|
2659 obj->initSlotUnchecked(oldSpan, UndefinedValue()); |
|
2660 else |
|
2661 obj->initializeSlotRange(oldSpan, newSpan - oldSpan); |
|
2662 } else { |
|
2663 /* Trigger write barriers on the old slots before reallocating. */ |
|
2664 obj->prepareSlotRangeForOverwrite(newSpan, oldSpan); |
|
2665 obj->invalidateSlotRange(newSpan, oldSpan - newSpan); |
|
2666 |
|
2667 if (oldCount > newCount) |
|
2668 JSObject::shrinkSlots(cx, obj, oldCount, newCount); |
|
2669 } |
|
2670 |
|
2671 return true; |
|
2672 } |
|
2673 |
|
2674 /* static */ bool |
|
2675 JSObject::setLastProperty(ThreadSafeContext *cx, HandleObject obj, HandleShape shape) |
|
2676 { |
|
2677 JS_ASSERT(cx->isThreadLocal(obj)); |
|
2678 JS_ASSERT(!obj->inDictionaryMode()); |
|
2679 JS_ASSERT(!shape->inDictionary()); |
|
2680 JS_ASSERT(shape->compartment() == obj->compartment()); |
|
2681 JS_ASSERT(shape->numFixedSlots() == obj->numFixedSlots()); |
|
2682 |
|
2683 size_t oldSpan = obj->lastProperty()->slotSpan(); |
|
2684 size_t newSpan = shape->slotSpan(); |
|
2685 |
|
2686 if (oldSpan == newSpan) { |
|
2687 obj->shape_ = shape; |
|
2688 return true; |
|
2689 } |
|
2690 |
|
2691 if (!updateSlotsForSpan(cx, obj, oldSpan, newSpan)) |
|
2692 return false; |
|
2693 |
|
2694 obj->shape_ = shape; |
|
2695 return true; |
|
2696 } |
|
2697 |
|
2698 /* static */ bool |
|
2699 JSObject::setSlotSpan(ThreadSafeContext *cx, HandleObject obj, uint32_t span) |
|
2700 { |
|
2701 JS_ASSERT(cx->isThreadLocal(obj)); |
|
2702 JS_ASSERT(obj->inDictionaryMode()); |
|
2703 |
|
2704 size_t oldSpan = obj->lastProperty()->base()->slotSpan(); |
|
2705 if (oldSpan == span) |
|
2706 return true; |
|
2707 |
|
2708 if (!JSObject::updateSlotsForSpan(cx, obj, oldSpan, span)) |
|
2709 return false; |
|
2710 |
|
2711 obj->lastProperty()->base()->setSlotSpan(span); |
|
2712 return true; |
|
2713 } |
|
2714 |
|
2715 static HeapSlot * |
|
2716 AllocateSlots(ThreadSafeContext *cx, JSObject *obj, uint32_t nslots) |
|
2717 { |
|
2718 #ifdef JSGC_GENERATIONAL |
|
2719 if (cx->isJSContext()) |
|
2720 return cx->asJSContext()->runtime()->gcNursery.allocateSlots(cx->asJSContext(), obj, nslots); |
|
2721 #endif |
|
2722 return cx->pod_malloc<HeapSlot>(nslots); |
|
2723 } |
|
2724 |
|
2725 static HeapSlot * |
|
2726 ReallocateSlots(ThreadSafeContext *cx, JSObject *obj, HeapSlot *oldSlots, |
|
2727 uint32_t oldCount, uint32_t newCount) |
|
2728 { |
|
2729 #ifdef JSGC_GENERATIONAL |
|
2730 if (cx->isJSContext()) { |
|
2731 return cx->asJSContext()->runtime()->gcNursery.reallocateSlots(cx->asJSContext(), |
|
2732 obj, oldSlots, |
|
2733 oldCount, newCount); |
|
2734 } |
|
2735 #endif |
|
2736 return (HeapSlot *)cx->realloc_(oldSlots, oldCount * sizeof(HeapSlot), |
|
2737 newCount * sizeof(HeapSlot)); |
|
2738 } |
|
2739 |
|
2740 /* static */ bool |
|
2741 JSObject::growSlots(ThreadSafeContext *cx, HandleObject obj, uint32_t oldCount, uint32_t newCount) |
|
2742 { |
|
2743 JS_ASSERT(cx->isThreadLocal(obj)); |
|
2744 JS_ASSERT(newCount > oldCount); |
|
2745 JS_ASSERT_IF(!obj->is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN); |
|
2746 |
|
2747 /* |
|
2748 * Slot capacities are determined by the span of allocated objects. Due to |
|
2749 * the limited number of bits to store shape slots, object growth is |
|
2750 * throttled well before the slot capacity can overflow. |
|
2751 */ |
|
2752 JS_ASSERT(newCount < NELEMENTS_LIMIT); |
|
2753 |
|
2754 /* |
|
2755 * If we are allocating slots for an object whose type is always created |
|
2756 * by calling 'new' on a particular script, bump the GC kind for that |
|
2757 * type to give these objects a larger number of fixed slots when future |
|
2758 * objects are constructed. |
|
2759 */ |
|
2760 if (!obj->hasLazyType() && !oldCount && obj->type()->hasNewScript()) { |
|
2761 JSObject *oldTemplate = obj->type()->newScript()->templateObject; |
|
2762 gc::AllocKind kind = gc::GetGCObjectFixedSlotsKind(oldTemplate->numFixedSlots()); |
|
2763 uint32_t newScriptSlots = gc::GetGCKindSlots(kind); |
|
2764 if (newScriptSlots == obj->numFixedSlots() && |
|
2765 gc::TryIncrementAllocKind(&kind) && |
|
2766 cx->isJSContext()) |
|
2767 { |
|
2768 JSContext *ncx = cx->asJSContext(); |
|
2769 AutoEnterAnalysis enter(ncx); |
|
2770 |
|
2771 Rooted<TypeObject*> typeObj(cx, obj->type()); |
|
2772 RootedShape shape(cx, oldTemplate->lastProperty()); |
|
2773 JSObject *reshapedObj = NewReshapedObject(ncx, typeObj, obj->getParent(), kind, shape, |
|
2774 MaybeSingletonObject); |
|
2775 if (!reshapedObj) |
|
2776 return false; |
|
2777 |
|
2778 typeObj->newScript()->templateObject = reshapedObj; |
|
2779 typeObj->markStateChange(ncx); |
|
2780 } |
|
2781 } |
|
2782 |
|
2783 if (!oldCount) { |
|
2784 obj->slots = AllocateSlots(cx, obj, newCount); |
|
2785 if (!obj->slots) |
|
2786 return false; |
|
2787 Debug_SetSlotRangeToCrashOnTouch(obj->slots, newCount); |
|
2788 return true; |
|
2789 } |
|
2790 |
|
2791 HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); |
|
2792 if (!newslots) |
|
2793 return false; /* Leave slots at its old size. */ |
|
2794 |
|
2795 obj->slots = newslots; |
|
2796 |
|
2797 Debug_SetSlotRangeToCrashOnTouch(obj->slots + oldCount, newCount - oldCount); |
|
2798 |
|
2799 return true; |
|
2800 } |
|
2801 |
|
2802 static void |
|
2803 FreeSlots(ThreadSafeContext *cx, HeapSlot *slots) |
|
2804 { |
|
2805 // Note: threads without a JSContext do not have access to nursery allocated things. |
|
2806 #ifdef JSGC_GENERATIONAL |
|
2807 if (cx->isJSContext()) |
|
2808 return cx->asJSContext()->runtime()->gcNursery.freeSlots(cx->asJSContext(), slots); |
|
2809 #endif |
|
2810 js_free(slots); |
|
2811 } |
|
2812 |
|
2813 /* static */ void |
|
2814 JSObject::shrinkSlots(ThreadSafeContext *cx, HandleObject obj, uint32_t oldCount, uint32_t newCount) |
|
2815 { |
|
2816 JS_ASSERT(cx->isThreadLocal(obj)); |
|
2817 JS_ASSERT(newCount < oldCount); |
|
2818 |
|
2819 if (newCount == 0) { |
|
2820 FreeSlots(cx, obj->slots); |
|
2821 obj->slots = nullptr; |
|
2822 return; |
|
2823 } |
|
2824 |
|
2825 JS_ASSERT_IF(!obj->is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN); |
|
2826 |
|
2827 HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); |
|
2828 if (!newslots) |
|
2829 return; /* Leave slots at its old size. */ |
|
2830 |
|
2831 obj->slots = newslots; |
|
2832 } |
|
2833 |
|
2834 /* static */ bool |
|
2835 JSObject::sparsifyDenseElement(ExclusiveContext *cx, HandleObject obj, uint32_t index) |
|
2836 { |
|
2837 RootedValue value(cx, obj->getDenseElement(index)); |
|
2838 JS_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); |
|
2839 |
|
2840 JSObject::removeDenseElementForSparseIndex(cx, obj, index); |
|
2841 |
|
2842 uint32_t slot = obj->slotSpan(); |
|
2843 if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) { |
|
2844 obj->setDenseElement(index, value); |
|
2845 return false; |
|
2846 } |
|
2847 |
|
2848 JS_ASSERT(slot == obj->slotSpan() - 1); |
|
2849 obj->initSlot(slot, value); |
|
2850 |
|
2851 return true; |
|
2852 } |
|
2853 |
|
2854 /* static */ bool |
|
2855 JSObject::sparsifyDenseElements(js::ExclusiveContext *cx, HandleObject obj) |
|
2856 { |
|
2857 uint32_t initialized = obj->getDenseInitializedLength(); |
|
2858 |
|
2859 /* Create new properties with the value of non-hole dense elements. */ |
|
2860 for (uint32_t i = 0; i < initialized; i++) { |
|
2861 if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) |
|
2862 continue; |
|
2863 |
|
2864 if (!sparsifyDenseElement(cx, obj, i)) |
|
2865 return false; |
|
2866 } |
|
2867 |
|
2868 if (initialized) |
|
2869 obj->setDenseInitializedLength(0); |
|
2870 |
|
2871 /* |
|
2872 * Reduce storage for dense elements which are now holes. Explicitly mark |
|
2873 * the elements capacity as zero, so that any attempts to add dense |
|
2874 * elements will be caught in ensureDenseElements. |
|
2875 */ |
|
2876 if (obj->getDenseCapacity()) { |
|
2877 obj->shrinkElements(cx, 0); |
|
2878 obj->getElementsHeader()->capacity = 0; |
|
2879 } |
|
2880 |
|
2881 return true; |
|
2882 } |
|
2883 |
|
2884 bool |
|
2885 JSObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint) |
|
2886 { |
|
2887 JS_ASSERT(isNative()); |
|
2888 JS_ASSERT(requiredCapacity > MIN_SPARSE_INDEX); |
|
2889 |
|
2890 uint32_t cap = getDenseCapacity(); |
|
2891 JS_ASSERT(requiredCapacity >= cap); |
|
2892 |
|
2893 if (requiredCapacity >= NELEMENTS_LIMIT) |
|
2894 return true; |
|
2895 |
|
2896 uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; |
|
2897 if (newElementsHint >= minimalDenseCount) |
|
2898 return false; |
|
2899 minimalDenseCount -= newElementsHint; |
|
2900 |
|
2901 if (minimalDenseCount > cap) |
|
2902 return true; |
|
2903 |
|
2904 uint32_t len = getDenseInitializedLength(); |
|
2905 const Value *elems = getDenseElements(); |
|
2906 for (uint32_t i = 0; i < len; i++) { |
|
2907 if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) |
|
2908 return false; |
|
2909 } |
|
2910 return true; |
|
2911 } |
|
2912 |
|
2913 /* static */ JSObject::EnsureDenseResult |
|
2914 JSObject::maybeDensifySparseElements(js::ExclusiveContext *cx, HandleObject obj) |
|
2915 { |
|
2916 /* |
|
2917 * Wait until after the object goes into dictionary mode, which must happen |
|
2918 * when sparsely packing any array with more than MIN_SPARSE_INDEX elements |
|
2919 * (see PropertyTree::MAX_HEIGHT). |
|
2920 */ |
|
2921 if (!obj->inDictionaryMode()) |
|
2922 return ED_SPARSE; |
|
2923 |
|
2924 /* |
|
2925 * Only measure the number of indexed properties every log(n) times when |
|
2926 * populating the object. |
|
2927 */ |
|
2928 uint32_t slotSpan = obj->slotSpan(); |
|
2929 if (slotSpan != RoundUpPow2(slotSpan)) |
|
2930 return ED_SPARSE; |
|
2931 |
|
2932 /* Watch for conditions under which an object's elements cannot be dense. */ |
|
2933 if (!obj->nonProxyIsExtensible() || obj->watched()) |
|
2934 return ED_SPARSE; |
|
2935 |
|
2936 /* |
|
2937 * The indexes in the object need to be sufficiently dense before they can |
|
2938 * be converted to dense mode. |
|
2939 */ |
|
2940 uint32_t numDenseElements = 0; |
|
2941 uint32_t newInitializedLength = 0; |
|
2942 |
|
2943 RootedShape shape(cx, obj->lastProperty()); |
|
2944 while (!shape->isEmptyShape()) { |
|
2945 uint32_t index; |
|
2946 if (js_IdIsIndex(shape->propid(), &index)) { |
|
2947 if (shape->attributes() == JSPROP_ENUMERATE && |
|
2948 shape->hasDefaultGetter() && |
|
2949 shape->hasDefaultSetter()) |
|
2950 { |
|
2951 numDenseElements++; |
|
2952 newInitializedLength = Max(newInitializedLength, index + 1); |
|
2953 } else { |
|
2954 /* |
|
2955 * For simplicity, only densify the object if all indexed |
|
2956 * properties can be converted to dense elements. |
|
2957 */ |
|
2958 return ED_SPARSE; |
|
2959 } |
|
2960 } |
|
2961 shape = shape->previous(); |
|
2962 } |
|
2963 |
|
2964 if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) |
|
2965 return ED_SPARSE; |
|
2966 |
|
2967 if (newInitializedLength >= NELEMENTS_LIMIT) |
|
2968 return ED_SPARSE; |
|
2969 |
|
2970 /* |
|
2971 * This object meets all necessary restrictions, convert all indexed |
|
2972 * properties into dense elements. |
|
2973 */ |
|
2974 |
|
2975 if (newInitializedLength > obj->getDenseCapacity()) { |
|
2976 if (!obj->growElements(cx, newInitializedLength)) |
|
2977 return ED_FAILED; |
|
2978 } |
|
2979 |
|
2980 obj->ensureDenseInitializedLength(cx, newInitializedLength, 0); |
|
2981 |
|
2982 RootedValue value(cx); |
|
2983 |
|
2984 shape = obj->lastProperty(); |
|
2985 while (!shape->isEmptyShape()) { |
|
2986 jsid id = shape->propid(); |
|
2987 uint32_t index; |
|
2988 if (js_IdIsIndex(id, &index)) { |
|
2989 value = obj->getSlot(shape->slot()); |
|
2990 |
|
2991 /* |
|
2992 * When removing a property from a dictionary, the specified |
|
2993 * property will be removed from the dictionary list and the |
|
2994 * last property will then be changed due to reshaping the object. |
|
2995 * Compute the next shape in the traverse, watching for such |
|
2996 * removals from the list. |
|
2997 */ |
|
2998 if (shape != obj->lastProperty()) { |
|
2999 shape = shape->previous(); |
|
3000 if (!obj->removeProperty(cx, id)) |
|
3001 return ED_FAILED; |
|
3002 } else { |
|
3003 if (!obj->removeProperty(cx, id)) |
|
3004 return ED_FAILED; |
|
3005 shape = obj->lastProperty(); |
|
3006 } |
|
3007 |
|
3008 obj->setDenseElement(index, value); |
|
3009 } else { |
|
3010 shape = shape->previous(); |
|
3011 } |
|
3012 } |
|
3013 |
|
3014 /* |
|
3015 * All indexed properties on the object are now dense, clear the indexed |
|
3016 * flag so that we will not start using sparse indexes again if we need |
|
3017 * to grow the object. |
|
3018 */ |
|
3019 if (!obj->clearFlag(cx, BaseShape::INDEXED)) |
|
3020 return ED_FAILED; |
|
3021 |
|
3022 return ED_OK; |
|
3023 } |
|
3024 |
|
3025 static ObjectElements * |
|
3026 AllocateElements(ThreadSafeContext *cx, JSObject *obj, uint32_t nelems) |
|
3027 { |
|
3028 #ifdef JSGC_GENERATIONAL |
|
3029 if (cx->isJSContext()) |
|
3030 return cx->asJSContext()->runtime()->gcNursery.allocateElements(cx->asJSContext(), obj, nelems); |
|
3031 #endif |
|
3032 |
|
3033 return static_cast<js::ObjectElements *>(cx->malloc_(nelems * sizeof(HeapValue))); |
|
3034 } |
|
3035 |
|
3036 static ObjectElements * |
|
3037 ReallocateElements(ThreadSafeContext *cx, JSObject *obj, ObjectElements *oldHeader, |
|
3038 uint32_t oldCount, uint32_t newCount) |
|
3039 { |
|
3040 #ifdef JSGC_GENERATIONAL |
|
3041 if (cx->isJSContext()) { |
|
3042 return cx->asJSContext()->runtime()->gcNursery.reallocateElements(cx->asJSContext(), obj, |
|
3043 oldHeader, oldCount, |
|
3044 newCount); |
|
3045 } |
|
3046 #endif |
|
3047 |
|
3048 return static_cast<js::ObjectElements *>(cx->realloc_(oldHeader, oldCount * sizeof(HeapSlot), |
|
3049 newCount * sizeof(HeapSlot))); |
|
3050 } |
|
3051 |
|
3052 bool |
|
3053 JSObject::growElements(ThreadSafeContext *cx, uint32_t newcap) |
|
3054 { |
|
3055 JS_ASSERT(nonProxyIsExtensible()); |
|
3056 JS_ASSERT(canHaveNonEmptyElements()); |
|
3057 |
|
3058 /* |
|
3059 * When an object with CAPACITY_DOUBLING_MAX or fewer elements needs to |
|
3060 * grow, double its capacity, to add N elements in amortized O(N) time. |
|
3061 * |
|
3062 * Above this limit, grow by 12.5% each time. Speed is still amortized |
|
3063 * O(N), with a higher constant factor, and we waste less space. |
|
3064 */ |
|
3065 static const size_t CAPACITY_DOUBLING_MAX = 1024 * 1024; |
|
3066 static const size_t CAPACITY_CHUNK = CAPACITY_DOUBLING_MAX / sizeof(Value); |
|
3067 |
|
3068 uint32_t oldcap = getDenseCapacity(); |
|
3069 JS_ASSERT(oldcap <= newcap); |
|
3070 |
|
3071 uint32_t nextsize = (oldcap <= CAPACITY_DOUBLING_MAX) |
|
3072 ? oldcap * 2 |
|
3073 : oldcap + (oldcap >> 3); |
|
3074 |
|
3075 uint32_t actualCapacity; |
|
3076 if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) { |
|
3077 JS_ASSERT(newcap <= as<ArrayObject>().length()); |
|
3078 // Preserve the |capacity <= length| invariant for arrays with |
|
3079 // non-writable length. See also js::ArraySetLength which initially |
|
3080 // enforces this requirement. |
|
3081 actualCapacity = newcap; |
|
3082 } else { |
|
3083 actualCapacity = Max(newcap, nextsize); |
|
3084 if (actualCapacity >= CAPACITY_CHUNK) |
|
3085 actualCapacity = JS_ROUNDUP(actualCapacity, CAPACITY_CHUNK); |
|
3086 else if (actualCapacity < SLOT_CAPACITY_MIN) |
|
3087 actualCapacity = SLOT_CAPACITY_MIN; |
|
3088 |
|
3089 /* Don't let nelements get close to wrapping around uint32_t. */ |
|
3090 if (actualCapacity >= NELEMENTS_LIMIT || actualCapacity < oldcap || actualCapacity < newcap) |
|
3091 return false; |
|
3092 } |
|
3093 |
|
3094 uint32_t initlen = getDenseInitializedLength(); |
|
3095 uint32_t oldAllocated = oldcap + ObjectElements::VALUES_PER_HEADER; |
|
3096 uint32_t newAllocated = actualCapacity + ObjectElements::VALUES_PER_HEADER; |
|
3097 |
|
3098 ObjectElements *newheader; |
|
3099 if (hasDynamicElements()) { |
|
3100 newheader = ReallocateElements(cx, this, getElementsHeader(), oldAllocated, newAllocated); |
|
3101 if (!newheader) |
|
3102 return false; /* Leave elements as its old size. */ |
|
3103 } else { |
|
3104 newheader = AllocateElements(cx, this, newAllocated); |
|
3105 if (!newheader) |
|
3106 return false; /* Leave elements as its old size. */ |
|
3107 js_memcpy(newheader, getElementsHeader(), |
|
3108 (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value)); |
|
3109 } |
|
3110 |
|
3111 newheader->capacity = actualCapacity; |
|
3112 elements = newheader->elements(); |
|
3113 |
|
3114 Debug_SetSlotRangeToCrashOnTouch(elements + initlen, actualCapacity - initlen); |
|
3115 |
|
3116 return true; |
|
3117 } |
|
3118 |
|
3119 void |
|
3120 JSObject::shrinkElements(ThreadSafeContext *cx, uint32_t newcap) |
|
3121 { |
|
3122 JS_ASSERT(cx->isThreadLocal(this)); |
|
3123 JS_ASSERT(canHaveNonEmptyElements()); |
|
3124 |
|
3125 uint32_t oldcap = getDenseCapacity(); |
|
3126 JS_ASSERT(newcap <= oldcap); |
|
3127 |
|
3128 // Don't shrink elements below the minimum capacity. |
|
3129 if (oldcap <= SLOT_CAPACITY_MIN || !hasDynamicElements()) |
|
3130 return; |
|
3131 |
|
3132 newcap = Max(newcap, SLOT_CAPACITY_MIN); |
|
3133 |
|
3134 uint32_t oldAllocated = oldcap + ObjectElements::VALUES_PER_HEADER; |
|
3135 uint32_t newAllocated = newcap + ObjectElements::VALUES_PER_HEADER; |
|
3136 |
|
3137 ObjectElements *newheader = ReallocateElements(cx, this, getElementsHeader(), |
|
3138 oldAllocated, newAllocated); |
|
3139 if (!newheader) { |
|
3140 cx->recoverFromOutOfMemory(); |
|
3141 return; // Leave elements at its old size. |
|
3142 } |
|
3143 |
|
3144 newheader->capacity = newcap; |
|
3145 elements = newheader->elements(); |
|
3146 } |
|
3147 |
|
3148 bool |
|
3149 js::SetClassAndProto(JSContext *cx, HandleObject obj, |
|
3150 const Class *clasp, Handle<js::TaggedProto> proto, |
|
3151 bool *succeeded) |
|
3152 { |
|
3153 /* |
|
3154 * Regenerate shapes for all of the scopes along the old prototype chain, |
|
3155 * in case any entries were filled by looking up through obj. Stop when a |
|
3156 * non-native object is found, prototype lookups will not be cached across |
|
3157 * these. |
|
3158 * |
|
3159 * How this shape change is done is very delicate; the change can be made |
|
3160 * either by marking the object's prototype as uncacheable (such that the |
|
3161 * property cache and JIT'ed ICs cannot assume the shape determines the |
|
3162 * prototype) or by just generating a new shape for the object. Choosing |
|
3163 * the former is bad if the object is on the prototype chain of other |
|
3164 * objects, as the uncacheable prototype can inhibit iterator caches on |
|
3165 * those objects and slow down prototype accesses. Choosing the latter is |
|
3166 * bad if there are many similar objects to this one which will have their |
|
3167 * prototype mutated, as the generateOwnShape forces the object into |
|
3168 * dictionary mode and similar property lineages will be repeatedly cloned. |
|
3169 * |
|
3170 * :XXX: bug 707717 make this code less brittle. |
|
3171 */ |
|
3172 *succeeded = false; |
|
3173 RootedObject oldproto(cx, obj); |
|
3174 while (oldproto && oldproto->isNative()) { |
|
3175 if (oldproto->hasSingletonType()) { |
|
3176 if (!oldproto->generateOwnShape(cx)) |
|
3177 return false; |
|
3178 } else { |
|
3179 if (!oldproto->setUncacheableProto(cx)) |
|
3180 return false; |
|
3181 } |
|
3182 oldproto = oldproto->getProto(); |
|
3183 } |
|
3184 |
|
3185 if (obj->hasSingletonType()) { |
|
3186 /* |
|
3187 * Just splice the prototype, but mark the properties as unknown for |
|
3188 * consistent behavior. |
|
3189 */ |
|
3190 if (!obj->splicePrototype(cx, clasp, proto)) |
|
3191 return false; |
|
3192 MarkTypeObjectUnknownProperties(cx, obj->type()); |
|
3193 *succeeded = true; |
|
3194 return true; |
|
3195 } |
|
3196 |
|
3197 if (proto.isObject()) { |
|
3198 RootedObject protoObj(cx, proto.toObject()); |
|
3199 if (!JSObject::setNewTypeUnknown(cx, clasp, protoObj)) |
|
3200 return false; |
|
3201 } |
|
3202 |
|
3203 TypeObject *type = cx->getNewType(clasp, proto); |
|
3204 if (!type) |
|
3205 return false; |
|
3206 |
|
3207 /* |
|
3208 * Setting __proto__ on an object that has escaped and may be referenced by |
|
3209 * other heap objects can only be done if the properties of both objects |
|
3210 * are unknown. Type sets containing this object will contain the original |
|
3211 * type but not the new type of the object, so we need to go and scan the |
|
3212 * entire compartment for type sets which have these objects and mark them |
|
3213 * as containing generic objects. |
|
3214 */ |
|
3215 MarkTypeObjectUnknownProperties(cx, obj->type(), true); |
|
3216 MarkTypeObjectUnknownProperties(cx, type, true); |
|
3217 |
|
3218 obj->setType(type); |
|
3219 |
|
3220 *succeeded = true; |
|
3221 return true; |
|
3222 } |
|
3223 |
|
3224 static bool |
|
3225 MaybeResolveConstructor(ExclusiveContext *cxArg, Handle<GlobalObject*> global, JSProtoKey key) |
|
3226 { |
|
3227 if (global->isStandardClassResolved(key)) |
|
3228 return true; |
|
3229 if (!cxArg->shouldBeJSContext()) |
|
3230 return false; |
|
3231 |
|
3232 JSContext *cx = cxArg->asJSContext(); |
|
3233 return GlobalObject::resolveConstructor(cx, global, key); |
|
3234 } |
|
3235 |
|
3236 bool |
|
3237 js::GetBuiltinConstructor(ExclusiveContext *cx, JSProtoKey key, MutableHandleObject objp) |
|
3238 { |
|
3239 MOZ_ASSERT(key != JSProto_Null); |
|
3240 Rooted<GlobalObject*> global(cx, cx->global()); |
|
3241 if (!MaybeResolveConstructor(cx, global, key)) |
|
3242 return false; |
|
3243 |
|
3244 objp.set(&global->getConstructor(key).toObject()); |
|
3245 return true; |
|
3246 } |
|
3247 |
|
3248 bool |
|
3249 js::GetBuiltinPrototype(ExclusiveContext *cx, JSProtoKey key, MutableHandleObject protop) |
|
3250 { |
|
3251 MOZ_ASSERT(key != JSProto_Null); |
|
3252 Rooted<GlobalObject*> global(cx, cx->global()); |
|
3253 if (!MaybeResolveConstructor(cx, global, key)) |
|
3254 return false; |
|
3255 |
|
3256 protop.set(&global->getPrototype(key).toObject()); |
|
3257 return true; |
|
3258 } |
|
3259 |
|
3260 static bool |
|
3261 IsStandardPrototype(JSObject *obj, JSProtoKey key) |
|
3262 { |
|
3263 GlobalObject &global = obj->global(); |
|
3264 Value v = global.getPrototype(key); |
|
3265 return v.isObject() && obj == &v.toObject(); |
|
3266 } |
|
3267 |
|
3268 JSProtoKey |
|
3269 JS::IdentifyStandardInstance(JSObject *obj) |
|
3270 { |
|
3271 // Note: The prototype shares its JSClass with instances. |
|
3272 JS_ASSERT(!obj->is<CrossCompartmentWrapperObject>()); |
|
3273 JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass()); |
|
3274 if (key != JSProto_Null && !IsStandardPrototype(obj, key)) |
|
3275 return key; |
|
3276 return JSProto_Null; |
|
3277 } |
|
3278 |
|
3279 JSProtoKey |
|
3280 JS::IdentifyStandardPrototype(JSObject *obj) |
|
3281 { |
|
3282 // Note: The prototype shares its JSClass with instances. |
|
3283 JS_ASSERT(!obj->is<CrossCompartmentWrapperObject>()); |
|
3284 JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass()); |
|
3285 if (key != JSProto_Null && IsStandardPrototype(obj, key)) |
|
3286 return key; |
|
3287 return JSProto_Null; |
|
3288 } |
|
3289 |
|
3290 JSProtoKey |
|
3291 JS::IdentifyStandardInstanceOrPrototype(JSObject *obj) |
|
3292 { |
|
3293 return JSCLASS_CACHED_PROTO_KEY(obj->getClass()); |
|
3294 } |
|
3295 |
|
3296 bool |
|
3297 js::FindClassObject(ExclusiveContext *cx, MutableHandleObject protop, const Class *clasp) |
|
3298 { |
|
3299 JSProtoKey protoKey = GetClassProtoKey(clasp); |
|
3300 if (protoKey != JSProto_Null) { |
|
3301 JS_ASSERT(JSProto_Null < protoKey); |
|
3302 JS_ASSERT(protoKey < JSProto_LIMIT); |
|
3303 return GetBuiltinConstructor(cx, protoKey, protop); |
|
3304 } |
|
3305 |
|
3306 JSAtom *atom = Atomize(cx, clasp->name, strlen(clasp->name)); |
|
3307 if (!atom) |
|
3308 return false; |
|
3309 RootedId id(cx, AtomToId(atom)); |
|
3310 |
|
3311 RootedObject pobj(cx); |
|
3312 RootedShape shape(cx); |
|
3313 if (!LookupNativeProperty(cx, cx->global(), id, &pobj, &shape)) |
|
3314 return false; |
|
3315 RootedValue v(cx); |
|
3316 if (shape && pobj->isNative()) { |
|
3317 if (shape->hasSlot()) |
|
3318 v = pobj->nativeGetSlot(shape->slot()); |
|
3319 } |
|
3320 if (v.isObject()) |
|
3321 protop.set(&v.toObject()); |
|
3322 return true; |
|
3323 } |
|
3324 |
|
3325 /* static */ bool |
|
3326 JSObject::allocSlot(ThreadSafeContext *cx, HandleObject obj, uint32_t *slotp) |
|
3327 { |
|
3328 JS_ASSERT(cx->isThreadLocal(obj)); |
|
3329 |
|
3330 uint32_t slot = obj->slotSpan(); |
|
3331 JS_ASSERT(slot >= JSSLOT_FREE(obj->getClass())); |
|
3332 |
|
3333 /* |
|
3334 * If this object is in dictionary mode, try to pull a free slot from the |
|
3335 * shape table's slot-number freelist. |
|
3336 */ |
|
3337 if (obj->inDictionaryMode()) { |
|
3338 ShapeTable &table = obj->lastProperty()->table(); |
|
3339 uint32_t last = table.freelist; |
|
3340 if (last != SHAPE_INVALID_SLOT) { |
|
3341 #ifdef DEBUG |
|
3342 JS_ASSERT(last < slot); |
|
3343 uint32_t next = obj->getSlot(last).toPrivateUint32(); |
|
3344 JS_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot); |
|
3345 #endif |
|
3346 |
|
3347 *slotp = last; |
|
3348 |
|
3349 const Value &vref = obj->getSlot(last); |
|
3350 table.freelist = vref.toPrivateUint32(); |
|
3351 obj->setSlot(last, UndefinedValue()); |
|
3352 return true; |
|
3353 } |
|
3354 } |
|
3355 |
|
3356 if (slot >= SHAPE_MAXIMUM_SLOT) { |
|
3357 js_ReportOutOfMemory(cx); |
|
3358 return false; |
|
3359 } |
|
3360 |
|
3361 *slotp = slot; |
|
3362 |
|
3363 if (obj->inDictionaryMode() && !setSlotSpan(cx, obj, slot + 1)) |
|
3364 return false; |
|
3365 |
|
3366 return true; |
|
3367 } |
|
3368 |
|
3369 void |
|
3370 JSObject::freeSlot(uint32_t slot) |
|
3371 { |
|
3372 JS_ASSERT(slot < slotSpan()); |
|
3373 |
|
3374 if (inDictionaryMode()) { |
|
3375 uint32_t &last = lastProperty()->table().freelist; |
|
3376 |
|
3377 /* Can't afford to check the whole freelist, but let's check the head. */ |
|
3378 JS_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot); |
|
3379 |
|
3380 /* |
|
3381 * Place all freed slots other than reserved slots (bug 595230) on the |
|
3382 * dictionary's free list. |
|
3383 */ |
|
3384 if (JSSLOT_FREE(getClass()) <= slot) { |
|
3385 JS_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan()); |
|
3386 setSlot(slot, PrivateUint32Value(last)); |
|
3387 last = slot; |
|
3388 return; |
|
3389 } |
|
3390 } |
|
3391 setSlot(slot, UndefinedValue()); |
|
3392 } |
|
3393 |
|
3394 static bool |
|
3395 PurgeProtoChain(ExclusiveContext *cx, JSObject *objArg, HandleId id) |
|
3396 { |
|
3397 /* Root locally so we can re-assign. */ |
|
3398 RootedObject obj(cx, objArg); |
|
3399 |
|
3400 RootedShape shape(cx); |
|
3401 while (obj) { |
|
3402 /* Lookups will not be cached through non-native protos. */ |
|
3403 if (!obj->isNative()) |
|
3404 break; |
|
3405 |
|
3406 shape = obj->nativeLookup(cx, id); |
|
3407 if (shape) { |
|
3408 if (!obj->shadowingShapeChange(cx, *shape)) |
|
3409 return false; |
|
3410 |
|
3411 obj->shadowingShapeChange(cx, *shape); |
|
3412 return true; |
|
3413 } |
|
3414 obj = obj->getProto(); |
|
3415 } |
|
3416 |
|
3417 return true; |
|
3418 } |
|
3419 |
|
3420 static bool |
|
3421 PurgeScopeChainHelper(ExclusiveContext *cx, HandleObject objArg, HandleId id) |
|
3422 { |
|
3423 /* Re-root locally so we can re-assign. */ |
|
3424 RootedObject obj(cx, objArg); |
|
3425 |
|
3426 JS_ASSERT(obj->isNative()); |
|
3427 JS_ASSERT(obj->isDelegate()); |
|
3428 |
|
3429 /* Lookups on integer ids cannot be cached through prototypes. */ |
|
3430 if (JSID_IS_INT(id)) |
|
3431 return true; |
|
3432 |
|
3433 PurgeProtoChain(cx, obj->getProto(), id); |
|
3434 |
|
3435 /* |
|
3436 * We must purge the scope chain only for Call objects as they are the only |
|
3437 * kind of cacheable non-global object that can gain properties after outer |
|
3438 * properties with the same names have been cached or traced. Call objects |
|
3439 * may gain such properties via eval introducing new vars; see bug 490364. |
|
3440 */ |
|
3441 if (obj->is<CallObject>()) { |
|
3442 while ((obj = obj->enclosingScope()) != nullptr) { |
|
3443 if (!PurgeProtoChain(cx, obj, id)) |
|
3444 return false; |
|
3445 } |
|
3446 } |
|
3447 |
|
3448 return true; |
|
3449 } |
|
3450 |
|
3451 /* |
|
3452 * PurgeScopeChain does nothing if obj is not itself a prototype or parent |
|
3453 * scope, else it reshapes the scope and prototype chains it links. It calls |
|
3454 * PurgeScopeChainHelper, which asserts that obj is flagged as a delegate |
|
3455 * (i.e., obj has ever been on a prototype or parent chain). |
|
3456 */ |
|
3457 static inline bool |
|
3458 PurgeScopeChain(ExclusiveContext *cx, JS::HandleObject obj, JS::HandleId id) |
|
3459 { |
|
3460 if (obj->isDelegate()) |
|
3461 return PurgeScopeChainHelper(cx, obj, id); |
|
3462 return true; |
|
3463 } |
|
3464 |
|
3465 bool |
|
3466 baseops::DefineGeneric(ExclusiveContext *cx, HandleObject obj, HandleId id, HandleValue value, |
|
3467 PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
|
3468 { |
|
3469 return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); |
|
3470 } |
|
3471 |
|
3472 /* static */ bool |
|
3473 JSObject::defineGeneric(ExclusiveContext *cx, HandleObject obj, |
|
3474 HandleId id, HandleValue value, |
|
3475 JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) |
|
3476 { |
|
3477 JS_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); |
|
3478 js::DefineGenericOp op = obj->getOps()->defineGeneric; |
|
3479 if (op) { |
|
3480 if (!cx->shouldBeJSContext()) |
|
3481 return false; |
|
3482 return op(cx->asJSContext(), obj, id, value, getter, setter, attrs); |
|
3483 } |
|
3484 return baseops::DefineGeneric(cx, obj, id, value, getter, setter, attrs); |
|
3485 } |
|
3486 |
|
3487 /* static */ bool |
|
3488 JSObject::defineProperty(ExclusiveContext *cx, HandleObject obj, |
|
3489 PropertyName *name, HandleValue value, |
|
3490 JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) |
|
3491 { |
|
3492 RootedId id(cx, NameToId(name)); |
|
3493 return defineGeneric(cx, obj, id, value, getter, setter, attrs); |
|
3494 } |
|
3495 |
|
3496 bool |
|
3497 baseops::DefineElement(ExclusiveContext *cx, HandleObject obj, uint32_t index, HandleValue value, |
|
3498 PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
|
3499 { |
|
3500 RootedId id(cx); |
|
3501 if (index <= JSID_INT_MAX) { |
|
3502 id = INT_TO_JSID(index); |
|
3503 return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); |
|
3504 } |
|
3505 |
|
3506 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
|
3507 |
|
3508 if (!IndexToId(cx, index, &id)) |
|
3509 return false; |
|
3510 |
|
3511 return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); |
|
3512 } |
|
3513 |
|
3514 /* static */ bool |
|
3515 JSObject::defineElement(ExclusiveContext *cx, HandleObject obj, |
|
3516 uint32_t index, HandleValue value, |
|
3517 JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) |
|
3518 { |
|
3519 js::DefineElementOp op = obj->getOps()->defineElement; |
|
3520 if (op) { |
|
3521 if (!cx->shouldBeJSContext()) |
|
3522 return false; |
|
3523 return op(cx->asJSContext(), obj, index, value, getter, setter, attrs); |
|
3524 } |
|
3525 return baseops::DefineElement(cx, obj, index, value, getter, setter, attrs); |
|
3526 } |
|
3527 |
|
3528 Shape * |
|
3529 JSObject::addDataProperty(ExclusiveContext *cx, jsid idArg, uint32_t slot, unsigned attrs) |
|
3530 { |
|
3531 JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); |
|
3532 RootedObject self(cx, this); |
|
3533 RootedId id(cx, idArg); |
|
3534 return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); |
|
3535 } |
|
3536 |
|
3537 Shape * |
|
3538 JSObject::addDataProperty(ExclusiveContext *cx, HandlePropertyName name, |
|
3539 uint32_t slot, unsigned attrs) |
|
3540 { |
|
3541 JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); |
|
3542 RootedObject self(cx, this); |
|
3543 RootedId id(cx, NameToId(name)); |
|
3544 return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); |
|
3545 } |
|
3546 |
|
3547 /* |
|
3548 * Backward compatibility requires allowing addProperty hooks to mutate the |
|
3549 * nominal initial value of a slotful property, while GC safety wants that |
|
3550 * value to be stored before the call-out through the hook. Optimize to do |
|
3551 * both while saving cycles for classes that stub their addProperty hook. |
|
3552 */ |
|
3553 template <ExecutionMode mode> |
|
3554 static inline bool |
|
3555 CallAddPropertyHook(typename ExecutionModeTraits<mode>::ExclusiveContextType cxArg, |
|
3556 const Class *clasp, HandleObject obj, HandleShape shape, |
|
3557 HandleValue nominal) |
|
3558 { |
|
3559 if (clasp->addProperty != JS_PropertyStub) { |
|
3560 if (mode == ParallelExecution) |
|
3561 return false; |
|
3562 |
|
3563 ExclusiveContext *cx = cxArg->asExclusiveContext(); |
|
3564 if (!cx->shouldBeJSContext()) |
|
3565 return false; |
|
3566 |
|
3567 /* Make a local copy of value so addProperty can mutate its inout parameter. */ |
|
3568 RootedValue value(cx, nominal); |
|
3569 |
|
3570 Rooted<jsid> id(cx, shape->propid()); |
|
3571 if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { |
|
3572 obj->removeProperty(cx, shape->propid()); |
|
3573 return false; |
|
3574 } |
|
3575 if (value.get() != nominal) { |
|
3576 if (shape->hasSlot()) |
|
3577 obj->nativeSetSlotWithType(cx, shape, value); |
|
3578 } |
|
3579 } |
|
3580 return true; |
|
3581 } |
|
3582 |
|
3583 template <ExecutionMode mode> |
|
3584 static inline bool |
|
3585 CallAddPropertyHookDense(typename ExecutionModeTraits<mode>::ExclusiveContextType cxArg, |
|
3586 const Class *clasp, HandleObject obj, uint32_t index, |
|
3587 HandleValue nominal) |
|
3588 { |
|
3589 /* Inline addProperty for array objects. */ |
|
3590 if (obj->is<ArrayObject>()) { |
|
3591 ArrayObject *arr = &obj->as<ArrayObject>(); |
|
3592 uint32_t length = arr->length(); |
|
3593 if (index >= length) { |
|
3594 if (mode == ParallelExecution) { |
|
3595 /* We cannot deal with overflows in parallel. */ |
|
3596 if (length > INT32_MAX) |
|
3597 return false; |
|
3598 arr->setLengthInt32(index + 1); |
|
3599 } else { |
|
3600 arr->setLength(cxArg->asExclusiveContext(), index + 1); |
|
3601 } |
|
3602 } |
|
3603 return true; |
|
3604 } |
|
3605 |
|
3606 if (clasp->addProperty != JS_PropertyStub) { |
|
3607 if (mode == ParallelExecution) |
|
3608 return false; |
|
3609 |
|
3610 ExclusiveContext *cx = cxArg->asExclusiveContext(); |
|
3611 if (!cx->shouldBeJSContext()) |
|
3612 return false; |
|
3613 |
|
3614 /* Make a local copy of value so addProperty can mutate its inout parameter. */ |
|
3615 RootedValue value(cx, nominal); |
|
3616 |
|
3617 Rooted<jsid> id(cx, INT_TO_JSID(index)); |
|
3618 if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { |
|
3619 obj->setDenseElementHole(cx, index); |
|
3620 return false; |
|
3621 } |
|
3622 if (value.get() != nominal) |
|
3623 obj->setDenseElementWithType(cx, index, value); |
|
3624 } |
|
3625 |
|
3626 return true; |
|
3627 } |
|
3628 |
|
3629 template <ExecutionMode mode> |
|
3630 static bool |
|
3631 UpdateShapeTypeAndValue(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, |
|
3632 JSObject *obj, Shape *shape, const Value &value) |
|
3633 { |
|
3634 jsid id = shape->propid(); |
|
3635 if (shape->hasSlot()) { |
|
3636 if (mode == ParallelExecution) { |
|
3637 if (!obj->nativeSetSlotIfHasType(shape, value)) |
|
3638 return false; |
|
3639 } else { |
|
3640 obj->nativeSetSlotWithType(cx->asExclusiveContext(), shape, value); |
|
3641 } |
|
3642 } |
|
3643 if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter()) { |
|
3644 if (mode == ParallelExecution) { |
|
3645 if (!IsTypePropertyIdMarkedNonData(obj, id)) |
|
3646 return false; |
|
3647 } else { |
|
3648 MarkTypePropertyNonData(cx->asExclusiveContext(), obj, id); |
|
3649 } |
|
3650 } |
|
3651 if (!shape->writable()) { |
|
3652 if (mode == ParallelExecution) { |
|
3653 if (!IsTypePropertyIdMarkedNonWritable(obj, id)) |
|
3654 return false; |
|
3655 } else { |
|
3656 MarkTypePropertyNonWritable(cx->asExclusiveContext(), obj, id); |
|
3657 } |
|
3658 } |
|
3659 return true; |
|
3660 } |
|
3661 |
|
3662 template <ExecutionMode mode> |
|
3663 static inline bool |
|
3664 DefinePropertyOrElement(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, |
|
3665 HandleObject obj, HandleId id, |
|
3666 PropertyOp getter, StrictPropertyOp setter, |
|
3667 unsigned attrs, HandleValue value, |
|
3668 bool callSetterAfterwards, bool setterIsStrict) |
|
3669 { |
|
3670 /* Use dense storage for new indexed properties where possible. */ |
|
3671 if (JSID_IS_INT(id) && |
|
3672 getter == JS_PropertyStub && |
|
3673 setter == JS_StrictPropertyStub && |
|
3674 attrs == JSPROP_ENUMERATE && |
|
3675 (!obj->isIndexed() || !obj->nativeContainsPure(id)) && |
|
3676 !obj->is<TypedArrayObject>()) |
|
3677 { |
|
3678 uint32_t index = JSID_TO_INT(id); |
|
3679 bool definesPast; |
|
3680 if (!WouldDefinePastNonwritableLength(cx, obj, index, setterIsStrict, &definesPast)) |
|
3681 return false; |
|
3682 if (definesPast) |
|
3683 return true; |
|
3684 |
|
3685 JSObject::EnsureDenseResult result; |
|
3686 if (mode == ParallelExecution) { |
|
3687 if (obj->writeToIndexWouldMarkNotPacked(index)) |
|
3688 return false; |
|
3689 result = obj->ensureDenseElementsPreservePackedFlag(cx, index, 1); |
|
3690 } else { |
|
3691 result = obj->ensureDenseElements(cx->asExclusiveContext(), index, 1); |
|
3692 } |
|
3693 |
|
3694 if (result == JSObject::ED_FAILED) |
|
3695 return false; |
|
3696 if (result == JSObject::ED_OK) { |
|
3697 if (mode == ParallelExecution) { |
|
3698 if (!obj->setDenseElementIfHasType(index, value)) |
|
3699 return false; |
|
3700 } else { |
|
3701 obj->setDenseElementWithType(cx->asExclusiveContext(), index, value); |
|
3702 } |
|
3703 return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value); |
|
3704 } |
|
3705 } |
|
3706 |
|
3707 if (obj->is<ArrayObject>()) { |
|
3708 Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); |
|
3709 if (id == NameToId(cx->names().length)) { |
|
3710 if (mode == SequentialExecution && !cx->shouldBeJSContext()) |
|
3711 return false; |
|
3712 return ArraySetLength<mode>(ExecutionModeTraits<mode>::toContextType(cx), arr, id, |
|
3713 attrs, value, setterIsStrict); |
|
3714 } |
|
3715 |
|
3716 uint32_t index; |
|
3717 if (js_IdIsIndex(id, &index)) { |
|
3718 bool definesPast; |
|
3719 if (!WouldDefinePastNonwritableLength(cx, arr, index, setterIsStrict, &definesPast)) |
|
3720 return false; |
|
3721 if (definesPast) |
|
3722 return true; |
|
3723 } |
|
3724 } |
|
3725 |
|
3726 // Don't define new indexed properties on typed arrays. |
|
3727 if (obj->is<TypedArrayObject>()) { |
|
3728 uint64_t index; |
|
3729 if (IsTypedArrayIndex(id, &index)) |
|
3730 return true; |
|
3731 } |
|
3732 |
|
3733 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
|
3734 |
|
3735 RootedShape shape(cx, JSObject::putProperty<mode>(cx, obj, id, getter, setter, |
|
3736 SHAPE_INVALID_SLOT, attrs, 0)); |
|
3737 if (!shape) |
|
3738 return false; |
|
3739 |
|
3740 if (!UpdateShapeTypeAndValue<mode>(cx, obj, shape, value)) |
|
3741 return false; |
|
3742 |
|
3743 /* |
|
3744 * Clear any existing dense index after adding a sparse indexed property, |
|
3745 * and investigate converting the object to dense indexes. |
|
3746 */ |
|
3747 if (JSID_IS_INT(id)) { |
|
3748 if (mode == ParallelExecution) |
|
3749 return false; |
|
3750 |
|
3751 ExclusiveContext *ncx = cx->asExclusiveContext(); |
|
3752 uint32_t index = JSID_TO_INT(id); |
|
3753 JSObject::removeDenseElementForSparseIndex(ncx, obj, index); |
|
3754 JSObject::EnsureDenseResult result = JSObject::maybeDensifySparseElements(ncx, obj); |
|
3755 if (result == JSObject::ED_FAILED) |
|
3756 return false; |
|
3757 if (result == JSObject::ED_OK) { |
|
3758 JS_ASSERT(setter == JS_StrictPropertyStub); |
|
3759 return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value); |
|
3760 } |
|
3761 } |
|
3762 |
|
3763 if (!CallAddPropertyHook<mode>(cx, obj->getClass(), obj, shape, value)) |
|
3764 return false; |
|
3765 |
|
3766 if (callSetterAfterwards && setter != JS_StrictPropertyStub) { |
|
3767 if (!cx->shouldBeJSContext()) |
|
3768 return false; |
|
3769 RootedValue nvalue(cx, value); |
|
3770 return NativeSet<mode>(ExecutionModeTraits<mode>::toContextType(cx), |
|
3771 obj, obj, shape, setterIsStrict, &nvalue); |
|
3772 } |
|
3773 return true; |
|
3774 } |
|
3775 |
|
3776 static bool |
|
3777 NativeLookupOwnProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, |
|
3778 MutableHandle<Shape*> shapep); |
|
3779 |
|
3780 bool |
|
3781 js::DefineNativeProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, HandleValue value, |
|
3782 PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
|
3783 { |
|
3784 JS_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); |
|
3785 |
|
3786 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
|
3787 |
|
3788 /* |
|
3789 * If defining a getter or setter, we must check for its counterpart and |
|
3790 * update the attributes and property ops. A getter or setter is really |
|
3791 * only half of a property. |
|
3792 */ |
|
3793 RootedShape shape(cx); |
|
3794 if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { |
|
3795 /* |
|
3796 * If we are defining a getter whose setter was already defined, or |
|
3797 * vice versa, finish the job via obj->changeProperty. |
|
3798 */ |
|
3799 if (!NativeLookupOwnProperty(cx, obj, id, &shape)) |
|
3800 return false; |
|
3801 if (shape) { |
|
3802 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
3803 if (obj->is<TypedArrayObject>()) { |
|
3804 /* Ignore getter/setter properties added to typed arrays. */ |
|
3805 return true; |
|
3806 } |
|
3807 if (!JSObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id))) |
|
3808 return false; |
|
3809 shape = obj->nativeLookup(cx, id); |
|
3810 } |
|
3811 if (shape->isAccessorDescriptor()) { |
|
3812 shape = JSObject::changeProperty<SequentialExecution>(cx, obj, shape, attrs, |
|
3813 JSPROP_GETTER | JSPROP_SETTER, |
|
3814 (attrs & JSPROP_GETTER) |
|
3815 ? getter |
|
3816 : shape->getter(), |
|
3817 (attrs & JSPROP_SETTER) |
|
3818 ? setter |
|
3819 : shape->setter()); |
|
3820 if (!shape) |
|
3821 return false; |
|
3822 } else { |
|
3823 shape = nullptr; |
|
3824 } |
|
3825 } |
|
3826 } |
|
3827 |
|
3828 /* |
|
3829 * Purge the property cache of any properties named by id that are about |
|
3830 * to be shadowed in obj's scope chain. |
|
3831 */ |
|
3832 if (!PurgeScopeChain(cx, obj, id)) |
|
3833 return false; |
|
3834 |
|
3835 /* Use the object's class getter and setter by default. */ |
|
3836 const Class *clasp = obj->getClass(); |
|
3837 if (!getter && !(attrs & JSPROP_GETTER)) |
|
3838 getter = clasp->getProperty; |
|
3839 if (!setter && !(attrs & JSPROP_SETTER)) |
|
3840 setter = clasp->setProperty; |
|
3841 |
|
3842 if (!shape) { |
|
3843 return DefinePropertyOrElement<SequentialExecution>(cx, obj, id, getter, setter, |
|
3844 attrs, value, false, false); |
|
3845 } |
|
3846 |
|
3847 JS_ALWAYS_TRUE(UpdateShapeTypeAndValue<SequentialExecution>(cx, obj, shape, value)); |
|
3848 |
|
3849 return CallAddPropertyHook<SequentialExecution>(cx, clasp, obj, shape, value); |
|
3850 } |
|
3851 |
|
3852 /* |
|
3853 * Call obj's resolve hook. |
|
3854 * |
|
3855 * cx, id, and flags are the parameters initially passed to the ongoing lookup; |
|
3856 * objp and propp are its out parameters. obj is an object along the prototype |
|
3857 * chain from where the lookup started. |
|
3858 * |
|
3859 * There are four possible outcomes: |
|
3860 * |
|
3861 * - On failure, report an error or exception and return false. |
|
3862 * |
|
3863 * - If we are already resolving a property of *curobjp, set *recursedp = true, |
|
3864 * and return true. |
|
3865 * |
|
3866 * - If the resolve hook finds or defines the sought property, set *objp and |
|
3867 * *propp appropriately, set *recursedp = false, and return true. |
|
3868 * |
|
3869 * - Otherwise no property was resolved. Set *propp = nullptr and |
|
3870 * *recursedp = false and return true. |
|
3871 */ |
|
3872 static MOZ_ALWAYS_INLINE bool |
|
3873 CallResolveOp(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp, |
|
3874 MutableHandleShape propp, bool *recursedp) |
|
3875 { |
|
3876 const Class *clasp = obj->getClass(); |
|
3877 JSResolveOp resolve = clasp->resolve; |
|
3878 |
|
3879 /* |
|
3880 * Avoid recursion on (obj, id) already being resolved on cx. |
|
3881 * |
|
3882 * Once we have successfully added an entry for (obj, key) to |
|
3883 * cx->resolvingTable, control must go through cleanup: before |
|
3884 * returning. But note that JS_DHASH_ADD may find an existing |
|
3885 * entry, in which case we bail to suppress runaway recursion. |
|
3886 */ |
|
3887 AutoResolving resolving(cx, obj, id); |
|
3888 if (resolving.alreadyStarted()) { |
|
3889 /* Already resolving id in obj -- suppress recursion. */ |
|
3890 *recursedp = true; |
|
3891 return true; |
|
3892 } |
|
3893 *recursedp = false; |
|
3894 |
|
3895 propp.set(nullptr); |
|
3896 |
|
3897 if (clasp->flags & JSCLASS_NEW_RESOLVE) { |
|
3898 JSNewResolveOp newresolve = reinterpret_cast<JSNewResolveOp>(resolve); |
|
3899 RootedObject obj2(cx, nullptr); |
|
3900 if (!newresolve(cx, obj, id, &obj2)) |
|
3901 return false; |
|
3902 |
|
3903 /* |
|
3904 * We trust the new style resolve hook to set obj2 to nullptr when |
|
3905 * the id cannot be resolved. But, when obj2 is not null, we do |
|
3906 * not assume that id must exist and do full nativeLookup for |
|
3907 * compatibility. |
|
3908 */ |
|
3909 if (!obj2) |
|
3910 return true; |
|
3911 |
|
3912 if (!obj2->isNative()) { |
|
3913 /* Whoops, newresolve handed back a foreign obj2. */ |
|
3914 JS_ASSERT(obj2 != obj); |
|
3915 return JSObject::lookupGeneric(cx, obj2, id, objp, propp); |
|
3916 } |
|
3917 |
|
3918 objp.set(obj2); |
|
3919 } else { |
|
3920 if (!resolve(cx, obj, id)) |
|
3921 return false; |
|
3922 |
|
3923 objp.set(obj); |
|
3924 } |
|
3925 |
|
3926 if (JSID_IS_INT(id) && objp->containsDenseElement(JSID_TO_INT(id))) { |
|
3927 MarkDenseOrTypedArrayElementFound<CanGC>(propp); |
|
3928 return true; |
|
3929 } |
|
3930 |
|
3931 Shape *shape; |
|
3932 if (!objp->nativeEmpty() && (shape = objp->nativeLookup(cx, id))) |
|
3933 propp.set(shape); |
|
3934 else |
|
3935 objp.set(nullptr); |
|
3936 |
|
3937 return true; |
|
3938 } |
|
3939 |
|
3940 template <AllowGC allowGC> |
|
3941 static MOZ_ALWAYS_INLINE bool |
|
3942 LookupOwnPropertyInline(ExclusiveContext *cx, |
|
3943 typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
|
3944 typename MaybeRooted<jsid, allowGC>::HandleType id, |
|
3945 typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, |
|
3946 typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp, |
|
3947 bool *donep) |
|
3948 { |
|
3949 // Check for a native dense element. |
|
3950 if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { |
|
3951 objp.set(obj); |
|
3952 MarkDenseOrTypedArrayElementFound<allowGC>(propp); |
|
3953 *donep = true; |
|
3954 return true; |
|
3955 } |
|
3956 |
|
3957 // Check for a typed array element. Integer lookups always finish here |
|
3958 // so that integer properties on the prototype are ignored even for out |
|
3959 // of bounds accesses. |
|
3960 if (obj->template is<TypedArrayObject>()) { |
|
3961 uint64_t index; |
|
3962 if (IsTypedArrayIndex(id, &index)) { |
|
3963 if (index < obj->template as<TypedArrayObject>().length()) { |
|
3964 objp.set(obj); |
|
3965 MarkDenseOrTypedArrayElementFound<allowGC>(propp); |
|
3966 } else { |
|
3967 objp.set(nullptr); |
|
3968 propp.set(nullptr); |
|
3969 } |
|
3970 *donep = true; |
|
3971 return true; |
|
3972 } |
|
3973 } |
|
3974 |
|
3975 // Check for a native property. |
|
3976 if (Shape *shape = obj->nativeLookup(cx, id)) { |
|
3977 objp.set(obj); |
|
3978 propp.set(shape); |
|
3979 *donep = true; |
|
3980 return true; |
|
3981 } |
|
3982 |
|
3983 // id was not found in obj. Try obj's resolve hook, if any. |
|
3984 if (obj->getClass()->resolve != JS_ResolveStub) { |
|
3985 if (!cx->shouldBeJSContext() || !allowGC) |
|
3986 return false; |
|
3987 |
|
3988 bool recursed; |
|
3989 if (!CallResolveOp(cx->asJSContext(), |
|
3990 MaybeRooted<JSObject*, allowGC>::toHandle(obj), |
|
3991 MaybeRooted<jsid, allowGC>::toHandle(id), |
|
3992 MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), |
|
3993 MaybeRooted<Shape*, allowGC>::toMutableHandle(propp), |
|
3994 &recursed)) |
|
3995 { |
|
3996 return false; |
|
3997 } |
|
3998 |
|
3999 if (recursed) { |
|
4000 objp.set(nullptr); |
|
4001 propp.set(nullptr); |
|
4002 *donep = true; |
|
4003 return true; |
|
4004 } |
|
4005 |
|
4006 if (propp) { |
|
4007 *donep = true; |
|
4008 return true; |
|
4009 } |
|
4010 } |
|
4011 |
|
4012 *donep = false; |
|
4013 return true; |
|
4014 } |
|
4015 |
|
4016 static bool |
|
4017 NativeLookupOwnProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, |
|
4018 MutableHandle<Shape*> shapep) |
|
4019 { |
|
4020 RootedObject pobj(cx); |
|
4021 bool done; |
|
4022 |
|
4023 if (!LookupOwnPropertyInline<CanGC>(cx, obj, id, &pobj, shapep, &done)) |
|
4024 return false; |
|
4025 if (!done || pobj != obj) |
|
4026 shapep.set(nullptr); |
|
4027 return true; |
|
4028 } |
|
4029 |
|
4030 template <AllowGC allowGC> |
|
4031 static MOZ_ALWAYS_INLINE bool |
|
4032 LookupPropertyInline(ExclusiveContext *cx, |
|
4033 typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
|
4034 typename MaybeRooted<jsid, allowGC>::HandleType id, |
|
4035 typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, |
|
4036 typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) |
|
4037 { |
|
4038 /* NB: The logic of this procedure is implicitly reflected in IonBuilder.cpp's |
|
4039 * |CanEffectlesslyCallLookupGenericOnObject| logic. |
|
4040 * If this changes, please remember to update the logic there as well. |
|
4041 */ |
|
4042 |
|
4043 /* Search scopes starting with obj and following the prototype link. */ |
|
4044 typename MaybeRooted<JSObject*, allowGC>::RootType current(cx, obj); |
|
4045 |
|
4046 while (true) { |
|
4047 bool done; |
|
4048 if (!LookupOwnPropertyInline<allowGC>(cx, current, id, objp, propp, &done)) |
|
4049 return false; |
|
4050 if (done) |
|
4051 return true; |
|
4052 |
|
4053 typename MaybeRooted<JSObject*, allowGC>::RootType proto(cx, current->getProto()); |
|
4054 |
|
4055 if (!proto) |
|
4056 break; |
|
4057 if (!proto->isNative()) { |
|
4058 if (!cx->shouldBeJSContext() || !allowGC) |
|
4059 return false; |
|
4060 return JSObject::lookupGeneric(cx->asJSContext(), |
|
4061 MaybeRooted<JSObject*, allowGC>::toHandle(proto), |
|
4062 MaybeRooted<jsid, allowGC>::toHandle(id), |
|
4063 MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), |
|
4064 MaybeRooted<Shape*, allowGC>::toMutableHandle(propp)); |
|
4065 } |
|
4066 |
|
4067 current = proto; |
|
4068 } |
|
4069 |
|
4070 objp.set(nullptr); |
|
4071 propp.set(nullptr); |
|
4072 return true; |
|
4073 } |
|
4074 |
|
4075 template <AllowGC allowGC> |
|
4076 bool |
|
4077 baseops::LookupProperty(ExclusiveContext *cx, |
|
4078 typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
|
4079 typename MaybeRooted<jsid, allowGC>::HandleType id, |
|
4080 typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, |
|
4081 typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) |
|
4082 { |
|
4083 return LookupPropertyInline<allowGC>(cx, obj, id, objp, propp); |
|
4084 } |
|
4085 |
|
4086 template bool |
|
4087 baseops::LookupProperty<CanGC>(ExclusiveContext *cx, HandleObject obj, HandleId id, |
|
4088 MutableHandleObject objp, MutableHandleShape propp); |
|
4089 |
|
4090 template bool |
|
4091 baseops::LookupProperty<NoGC>(ExclusiveContext *cx, JSObject *obj, jsid id, |
|
4092 FakeMutableHandle<JSObject*> objp, |
|
4093 FakeMutableHandle<Shape*> propp); |
|
4094 |
|
4095 /* static */ bool |
|
4096 JSObject::lookupGeneric(JSContext *cx, HandleObject obj, js::HandleId id, |
|
4097 MutableHandleObject objp, MutableHandleShape propp) |
|
4098 { |
|
4099 /* |
|
4100 * NB: The logic of lookupGeneric is implicitly reflected in IonBuilder.cpp's |
|
4101 * |CanEffectlesslyCallLookupGenericOnObject| logic. |
|
4102 * If this changes, please remember to update the logic there as well. |
|
4103 */ |
|
4104 LookupGenericOp op = obj->getOps()->lookupGeneric; |
|
4105 if (op) |
|
4106 return op(cx, obj, id, objp, propp); |
|
4107 return baseops::LookupProperty<js::CanGC>(cx, obj, id, objp, propp); |
|
4108 } |
|
4109 |
|
4110 bool |
|
4111 baseops::LookupElement(JSContext *cx, HandleObject obj, uint32_t index, |
|
4112 MutableHandleObject objp, MutableHandleShape propp) |
|
4113 { |
|
4114 RootedId id(cx); |
|
4115 if (!IndexToId(cx, index, &id)) |
|
4116 return false; |
|
4117 |
|
4118 return LookupPropertyInline<CanGC>(cx, obj, id, objp, propp); |
|
4119 } |
|
4120 |
|
4121 bool |
|
4122 js::LookupNativeProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, |
|
4123 MutableHandleObject objp, MutableHandleShape propp) |
|
4124 { |
|
4125 return LookupPropertyInline<CanGC>(cx, obj, id, objp, propp); |
|
4126 } |
|
4127 |
|
4128 bool |
|
4129 js::LookupName(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, |
|
4130 MutableHandleObject objp, MutableHandleObject pobjp, MutableHandleShape propp) |
|
4131 { |
|
4132 RootedId id(cx, NameToId(name)); |
|
4133 |
|
4134 for (RootedObject scope(cx, scopeChain); scope; scope = scope->enclosingScope()) { |
|
4135 if (!JSObject::lookupGeneric(cx, scope, id, pobjp, propp)) |
|
4136 return false; |
|
4137 if (propp) { |
|
4138 objp.set(scope); |
|
4139 return true; |
|
4140 } |
|
4141 } |
|
4142 |
|
4143 objp.set(nullptr); |
|
4144 pobjp.set(nullptr); |
|
4145 propp.set(nullptr); |
|
4146 return true; |
|
4147 } |
|
4148 |
|
4149 bool |
|
4150 js::LookupNameNoGC(JSContext *cx, PropertyName *name, JSObject *scopeChain, |
|
4151 JSObject **objp, JSObject **pobjp, Shape **propp) |
|
4152 { |
|
4153 AutoAssertNoException nogc(cx); |
|
4154 |
|
4155 JS_ASSERT(!*objp && !*pobjp && !*propp); |
|
4156 |
|
4157 for (JSObject *scope = scopeChain; scope; scope = scope->enclosingScope()) { |
|
4158 if (scope->getOps()->lookupGeneric) |
|
4159 return false; |
|
4160 if (!LookupPropertyInline<NoGC>(cx, scope, NameToId(name), pobjp, propp)) |
|
4161 return false; |
|
4162 if (*propp) { |
|
4163 *objp = scope; |
|
4164 return true; |
|
4165 } |
|
4166 } |
|
4167 |
|
4168 return true; |
|
4169 } |
|
4170 |
|
4171 bool |
|
4172 js::LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, |
|
4173 MutableHandleObject objp) |
|
4174 { |
|
4175 RootedId id(cx, NameToId(name)); |
|
4176 |
|
4177 RootedObject pobj(cx); |
|
4178 RootedShape prop(cx); |
|
4179 |
|
4180 RootedObject scope(cx, scopeChain); |
|
4181 for (; !scope->is<GlobalObject>(); scope = scope->enclosingScope()) { |
|
4182 if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop)) |
|
4183 return false; |
|
4184 if (prop) |
|
4185 break; |
|
4186 } |
|
4187 |
|
4188 objp.set(scope); |
|
4189 return true; |
|
4190 } |
|
4191 |
|
4192 template <AllowGC allowGC> |
|
4193 bool |
|
4194 js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup, |
|
4195 typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
|
4196 typename MaybeRooted<jsid, allowGC>::HandleType id, |
|
4197 typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, |
|
4198 typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) |
|
4199 { |
|
4200 if (lookup) { |
|
4201 if (!allowGC) |
|
4202 return false; |
|
4203 if (!lookup(cx, |
|
4204 MaybeRooted<JSObject*, allowGC>::toHandle(obj), |
|
4205 MaybeRooted<jsid, allowGC>::toHandle(id), |
|
4206 MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), |
|
4207 MaybeRooted<Shape*, allowGC>::toMutableHandle(propp))) |
|
4208 { |
|
4209 return false; |
|
4210 } |
|
4211 } else { |
|
4212 bool done; |
|
4213 if (!LookupOwnPropertyInline<allowGC>(cx, obj, id, objp, propp, &done)) |
|
4214 return false; |
|
4215 if (!done) { |
|
4216 objp.set(nullptr); |
|
4217 propp.set(nullptr); |
|
4218 return true; |
|
4219 } |
|
4220 } |
|
4221 |
|
4222 if (!propp) |
|
4223 return true; |
|
4224 |
|
4225 if (objp == obj) |
|
4226 return true; |
|
4227 |
|
4228 JSObject *outer = nullptr; |
|
4229 if (JSObjectOp op = objp->getClass()->ext.outerObject) { |
|
4230 if (!allowGC) |
|
4231 return false; |
|
4232 RootedObject inner(cx, objp); |
|
4233 outer = op(cx, inner); |
|
4234 if (!outer) |
|
4235 return false; |
|
4236 } |
|
4237 |
|
4238 if (outer != objp) |
|
4239 propp.set(nullptr); |
|
4240 return true; |
|
4241 } |
|
4242 |
|
4243 template bool |
|
4244 js::HasOwnProperty<CanGC>(JSContext *cx, LookupGenericOp lookup, |
|
4245 HandleObject obj, HandleId id, |
|
4246 MutableHandleObject objp, MutableHandleShape propp); |
|
4247 |
|
4248 template bool |
|
4249 js::HasOwnProperty<NoGC>(JSContext *cx, LookupGenericOp lookup, |
|
4250 JSObject *obj, jsid id, |
|
4251 FakeMutableHandle<JSObject*> objp, FakeMutableHandle<Shape*> propp); |
|
4252 |
|
4253 bool |
|
4254 js::HasOwnProperty(JSContext *cx, HandleObject obj, HandleId id, bool *resultp) |
|
4255 { |
|
4256 RootedObject pobj(cx); |
|
4257 RootedShape shape(cx); |
|
4258 if (!HasOwnProperty<CanGC>(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) |
|
4259 return false; |
|
4260 *resultp = (shape != nullptr); |
|
4261 return true; |
|
4262 } |
|
4263 |
|
4264 template <AllowGC allowGC> |
|
4265 static MOZ_ALWAYS_INLINE bool |
|
4266 NativeGetInline(JSContext *cx, |
|
4267 typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
|
4268 typename MaybeRooted<JSObject*, allowGC>::HandleType receiver, |
|
4269 typename MaybeRooted<JSObject*, allowGC>::HandleType pobj, |
|
4270 typename MaybeRooted<Shape*, allowGC>::HandleType shape, |
|
4271 typename MaybeRooted<Value, allowGC>::MutableHandleType vp) |
|
4272 { |
|
4273 JS_ASSERT(pobj->isNative()); |
|
4274 |
|
4275 if (shape->hasSlot()) { |
|
4276 vp.set(pobj->nativeGetSlot(shape->slot())); |
|
4277 JS_ASSERT(!vp.isMagic()); |
|
4278 JS_ASSERT_IF(!pobj->hasSingletonType() && |
|
4279 !pobj->template is<ScopeObject>() && |
|
4280 shape->hasDefaultGetter(), |
|
4281 js::types::TypeHasProperty(cx, pobj->type(), shape->propid(), vp)); |
|
4282 } else { |
|
4283 vp.setUndefined(); |
|
4284 } |
|
4285 if (shape->hasDefaultGetter()) |
|
4286 return true; |
|
4287 |
|
4288 { |
|
4289 jsbytecode *pc; |
|
4290 JSScript *script = cx->currentScript(&pc); |
|
4291 #ifdef JS_ION |
|
4292 if (script && script->hasBaselineScript()) { |
|
4293 switch (JSOp(*pc)) { |
|
4294 case JSOP_GETPROP: |
|
4295 case JSOP_CALLPROP: |
|
4296 case JSOP_LENGTH: |
|
4297 script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc)); |
|
4298 break; |
|
4299 default: |
|
4300 break; |
|
4301 } |
|
4302 } |
|
4303 #endif |
|
4304 } |
|
4305 |
|
4306 if (!allowGC) |
|
4307 return false; |
|
4308 |
|
4309 if (!shape->get(cx, |
|
4310 MaybeRooted<JSObject*, allowGC>::toHandle(receiver), |
|
4311 MaybeRooted<JSObject*, allowGC>::toHandle(obj), |
|
4312 MaybeRooted<JSObject*, allowGC>::toHandle(pobj), |
|
4313 MaybeRooted<Value, allowGC>::toMutableHandle(vp))) |
|
4314 { |
|
4315 return false; |
|
4316 } |
|
4317 |
|
4318 /* Update slotful shapes according to the value produced by the getter. */ |
|
4319 if (shape->hasSlot() && pobj->nativeContains(cx, shape)) |
|
4320 pobj->nativeSetSlot(shape->slot(), vp); |
|
4321 |
|
4322 return true; |
|
4323 } |
|
4324 |
|
4325 bool |
|
4326 js::NativeGet(JSContext *cx, Handle<JSObject*> obj, Handle<JSObject*> pobj, Handle<Shape*> shape, |
|
4327 MutableHandle<Value> vp) |
|
4328 { |
|
4329 return NativeGetInline<CanGC>(cx, obj, obj, pobj, shape, vp); |
|
4330 } |
|
4331 |
|
4332 template <ExecutionMode mode> |
|
4333 bool |
|
4334 js::NativeSet(typename ExecutionModeTraits<mode>::ContextType cxArg, |
|
4335 Handle<JSObject*> obj, Handle<JSObject*> receiver, |
|
4336 HandleShape shape, bool strict, MutableHandleValue vp) |
|
4337 { |
|
4338 JS_ASSERT(cxArg->isThreadLocal(obj)); |
|
4339 JS_ASSERT(obj->isNative()); |
|
4340 |
|
4341 if (shape->hasSlot()) { |
|
4342 /* If shape has a stub setter, just store vp. */ |
|
4343 if (shape->hasDefaultSetter()) { |
|
4344 if (mode == ParallelExecution) { |
|
4345 if (!obj->nativeSetSlotIfHasType(shape, vp)) |
|
4346 return false; |
|
4347 } else { |
|
4348 obj->nativeSetSlotWithType(cxArg->asExclusiveContext(), shape, vp); |
|
4349 } |
|
4350 |
|
4351 return true; |
|
4352 } |
|
4353 } |
|
4354 |
|
4355 if (mode == ParallelExecution) |
|
4356 return false; |
|
4357 JSContext *cx = cxArg->asJSContext(); |
|
4358 |
|
4359 if (!shape->hasSlot()) { |
|
4360 /* |
|
4361 * Allow API consumers to create shared properties with stub setters. |
|
4362 * Such properties effectively function as data descriptors which are |
|
4363 * not writable, so attempting to set such a property should do nothing |
|
4364 * or throw if we're in strict mode. |
|
4365 */ |
|
4366 if (!shape->hasGetterValue() && shape->hasDefaultSetter()) |
|
4367 return js_ReportGetterOnlyAssignment(cx, strict); |
|
4368 } |
|
4369 |
|
4370 RootedValue ovp(cx, vp); |
|
4371 |
|
4372 uint32_t sample = cx->runtime()->propertyRemovals; |
|
4373 if (!shape->set(cx, obj, receiver, strict, vp)) |
|
4374 return false; |
|
4375 |
|
4376 /* |
|
4377 * Update any slot for the shape with the value produced by the setter, |
|
4378 * unless the setter deleted the shape. |
|
4379 */ |
|
4380 if (shape->hasSlot() && |
|
4381 (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) || |
|
4382 obj->nativeContains(cx, shape))) |
|
4383 { |
|
4384 obj->setSlot(shape->slot(), vp); |
|
4385 } |
|
4386 |
|
4387 return true; |
|
4388 } |
|
4389 |
|
4390 template bool |
|
4391 js::NativeSet<SequentialExecution>(JSContext *cx, |
|
4392 Handle<JSObject*> obj, Handle<JSObject*> receiver, |
|
4393 HandleShape shape, bool strict, MutableHandleValue vp); |
|
4394 template bool |
|
4395 js::NativeSet<ParallelExecution>(ForkJoinContext *cx, |
|
4396 Handle<JSObject*> obj, Handle<JSObject*> receiver, |
|
4397 HandleShape shape, bool strict, MutableHandleValue vp); |
|
4398 |
|
4399 template <AllowGC allowGC> |
|
4400 static MOZ_ALWAYS_INLINE bool |
|
4401 GetPropertyHelperInline(JSContext *cx, |
|
4402 typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
|
4403 typename MaybeRooted<JSObject*, allowGC>::HandleType receiver, |
|
4404 typename MaybeRooted<jsid, allowGC>::HandleType id, |
|
4405 typename MaybeRooted<Value, allowGC>::MutableHandleType vp) |
|
4406 { |
|
4407 /* This call site is hot -- use the always-inlined variant of LookupNativeProperty(). */ |
|
4408 typename MaybeRooted<JSObject*, allowGC>::RootType obj2(cx); |
|
4409 typename MaybeRooted<Shape*, allowGC>::RootType shape(cx); |
|
4410 if (!LookupPropertyInline<allowGC>(cx, obj, id, &obj2, &shape)) |
|
4411 return false; |
|
4412 |
|
4413 if (!shape) { |
|
4414 if (!allowGC) |
|
4415 return false; |
|
4416 |
|
4417 vp.setUndefined(); |
|
4418 |
|
4419 if (!CallJSPropertyOp(cx, obj->getClass()->getProperty, |
|
4420 MaybeRooted<JSObject*, allowGC>::toHandle(obj), |
|
4421 MaybeRooted<jsid, allowGC>::toHandle(id), |
|
4422 MaybeRooted<Value, allowGC>::toMutableHandle(vp))) |
|
4423 { |
|
4424 return false; |
|
4425 } |
|
4426 |
|
4427 /* |
|
4428 * Give a strict warning if foo.bar is evaluated by a script for an |
|
4429 * object foo with no property named 'bar'. |
|
4430 */ |
|
4431 if (vp.isUndefined()) { |
|
4432 jsbytecode *pc = nullptr; |
|
4433 RootedScript script(cx, cx->currentScript(&pc)); |
|
4434 if (!pc) |
|
4435 return true; |
|
4436 JSOp op = (JSOp) *pc; |
|
4437 |
|
4438 if (op == JSOP_GETXPROP) { |
|
4439 /* Undefined property during a name lookup, report an error. */ |
|
4440 JSAutoByteString printable; |
|
4441 if (js_ValueToPrintable(cx, IdToValue(id), &printable)) |
|
4442 js_ReportIsNotDefined(cx, printable.ptr()); |
|
4443 return false; |
|
4444 } |
|
4445 |
|
4446 /* Don't warn if extra warnings not enabled or for random getprop operations. */ |
|
4447 if (!cx->options().extraWarnings() || (op != JSOP_GETPROP && op != JSOP_GETELEM)) |
|
4448 return true; |
|
4449 |
|
4450 /* Don't warn repeatedly for the same script. */ |
|
4451 if (!script || script->warnedAboutUndefinedProp()) |
|
4452 return true; |
|
4453 |
|
4454 /* |
|
4455 * Don't warn in self-hosted code (where the further presence of |
|
4456 * JS::ContextOptions::werror() would result in impossible-to-avoid |
|
4457 * errors to entirely-innocent client code). |
|
4458 */ |
|
4459 if (script->selfHosted()) |
|
4460 return true; |
|
4461 |
|
4462 /* We may just be checking if that object has an iterator. */ |
|
4463 if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic)) |
|
4464 return true; |
|
4465 |
|
4466 /* Do not warn about tests like (obj[prop] == undefined). */ |
|
4467 pc += js_CodeSpec[op].length; |
|
4468 if (Detecting(cx, script, pc)) |
|
4469 return true; |
|
4470 |
|
4471 unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT; |
|
4472 script->setWarnedAboutUndefinedProp(); |
|
4473 |
|
4474 /* Ok, bad undefined property reference: whine about it. */ |
|
4475 RootedValue val(cx, IdToValue(id)); |
|
4476 if (!js_ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP, |
|
4477 JSDVG_IGNORE_STACK, val, js::NullPtr(), |
|
4478 nullptr, nullptr)) |
|
4479 { |
|
4480 return false; |
|
4481 } |
|
4482 } |
|
4483 return true; |
|
4484 } |
|
4485 |
|
4486 if (!obj2->isNative()) { |
|
4487 if (!allowGC) |
|
4488 return false; |
|
4489 HandleObject obj2Handle = MaybeRooted<JSObject*, allowGC>::toHandle(obj2); |
|
4490 HandleObject receiverHandle = MaybeRooted<JSObject*, allowGC>::toHandle(receiver); |
|
4491 HandleId idHandle = MaybeRooted<jsid, allowGC>::toHandle(id); |
|
4492 MutableHandleValue vpHandle = MaybeRooted<Value, allowGC>::toMutableHandle(vp); |
|
4493 return obj2->template is<ProxyObject>() |
|
4494 ? Proxy::get(cx, obj2Handle, receiverHandle, idHandle, vpHandle) |
|
4495 : JSObject::getGeneric(cx, obj2Handle, obj2Handle, idHandle, vpHandle); |
|
4496 } |
|
4497 |
|
4498 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
4499 vp.set(obj2->getDenseOrTypedArrayElement(JSID_TO_INT(id))); |
|
4500 return true; |
|
4501 } |
|
4502 |
|
4503 /* This call site is hot -- use the always-inlined variant of NativeGet(). */ |
|
4504 if (!NativeGetInline<allowGC>(cx, obj, receiver, obj2, shape, vp)) |
|
4505 return false; |
|
4506 |
|
4507 return true; |
|
4508 } |
|
4509 |
|
4510 bool |
|
4511 baseops::GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, MutableHandleValue vp) |
|
4512 { |
|
4513 /* This call site is hot -- use the always-inlined variant of GetPropertyHelper(). */ |
|
4514 return GetPropertyHelperInline<CanGC>(cx, obj, receiver, id, vp); |
|
4515 } |
|
4516 |
|
4517 bool |
|
4518 baseops::GetPropertyNoGC(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Value *vp) |
|
4519 { |
|
4520 AutoAssertNoException nogc(cx); |
|
4521 return GetPropertyHelperInline<NoGC>(cx, obj, receiver, id, vp); |
|
4522 } |
|
4523 |
|
4524 static MOZ_ALWAYS_INLINE bool |
|
4525 LookupPropertyPureInline(JSObject *obj, jsid id, JSObject **objp, Shape **propp) |
|
4526 { |
|
4527 if (!obj->isNative()) |
|
4528 return false; |
|
4529 |
|
4530 JSObject *current = obj; |
|
4531 while (true) { |
|
4532 /* Search for a native dense element, typed array element, or property. */ |
|
4533 |
|
4534 if (JSID_IS_INT(id) && current->containsDenseElement(JSID_TO_INT(id))) { |
|
4535 *objp = current; |
|
4536 MarkDenseOrTypedArrayElementFound<NoGC>(propp); |
|
4537 return true; |
|
4538 } |
|
4539 |
|
4540 if (current->is<TypedArrayObject>()) { |
|
4541 uint64_t index; |
|
4542 if (IsTypedArrayIndex(id, &index)) { |
|
4543 if (index < obj->as<TypedArrayObject>().length()) { |
|
4544 *objp = current; |
|
4545 MarkDenseOrTypedArrayElementFound<NoGC>(propp); |
|
4546 } else { |
|
4547 *objp = nullptr; |
|
4548 *propp = nullptr; |
|
4549 } |
|
4550 return true; |
|
4551 } |
|
4552 } |
|
4553 |
|
4554 if (Shape *shape = current->nativeLookupPure(id)) { |
|
4555 *objp = current; |
|
4556 *propp = shape; |
|
4557 return true; |
|
4558 } |
|
4559 |
|
4560 /* Fail if there's a resolve hook. */ |
|
4561 if (current->getClass()->resolve != JS_ResolveStub) |
|
4562 return false; |
|
4563 |
|
4564 JSObject *proto = current->getProto(); |
|
4565 |
|
4566 if (!proto) |
|
4567 break; |
|
4568 if (!proto->isNative()) |
|
4569 return false; |
|
4570 |
|
4571 current = proto; |
|
4572 } |
|
4573 |
|
4574 *objp = nullptr; |
|
4575 *propp = nullptr; |
|
4576 return true; |
|
4577 } |
|
4578 |
|
4579 static MOZ_ALWAYS_INLINE bool |
|
4580 NativeGetPureInline(JSObject *pobj, Shape *shape, Value *vp) |
|
4581 { |
|
4582 JS_ASSERT(pobj->isNative()); |
|
4583 |
|
4584 if (shape->hasSlot()) { |
|
4585 *vp = pobj->nativeGetSlot(shape->slot()); |
|
4586 JS_ASSERT(!vp->isMagic()); |
|
4587 } else { |
|
4588 vp->setUndefined(); |
|
4589 } |
|
4590 |
|
4591 /* Fail if we have a custom getter. */ |
|
4592 return shape->hasDefaultGetter(); |
|
4593 } |
|
4594 |
|
4595 bool |
|
4596 js::LookupPropertyPure(JSObject *obj, jsid id, JSObject **objp, Shape **propp) |
|
4597 { |
|
4598 return LookupPropertyPureInline(obj, id, objp, propp); |
|
4599 } |
|
4600 |
|
4601 static inline bool |
|
4602 IdIsLength(ThreadSafeContext *cx, jsid id) |
|
4603 { |
|
4604 return JSID_IS_ATOM(id) && cx->names().length == JSID_TO_ATOM(id); |
|
4605 } |
|
4606 |
|
4607 /* |
|
4608 * A pure version of GetPropertyHelper that can be called from parallel code |
|
4609 * without locking. This code path cannot GC. This variant returns false |
|
4610 * whenever a side-effect might have occured in the effectful version. This |
|
4611 * includes, but is not limited to: |
|
4612 * |
|
4613 * - Any object in the lookup chain has a non-stub resolve hook. |
|
4614 * - Any object in the lookup chain is non-native. |
|
4615 * - The property has a getter. |
|
4616 */ |
|
4617 bool |
|
4618 js::GetPropertyPure(ThreadSafeContext *cx, JSObject *obj, jsid id, Value *vp) |
|
4619 { |
|
4620 /* Deal with native objects. */ |
|
4621 JSObject *obj2; |
|
4622 Shape *shape; |
|
4623 if (!LookupPropertyPureInline(obj, id, &obj2, &shape)) |
|
4624 return false; |
|
4625 |
|
4626 if (!shape) { |
|
4627 /* Fail if we have a non-stub class op hooks. */ |
|
4628 if (obj->getClass()->getProperty && obj->getClass()->getProperty != JS_PropertyStub) |
|
4629 return false; |
|
4630 |
|
4631 if (obj->getOps()->getElement) |
|
4632 return false; |
|
4633 |
|
4634 /* Vanilla native object, return undefined. */ |
|
4635 vp->setUndefined(); |
|
4636 return true; |
|
4637 } |
|
4638 |
|
4639 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
4640 *vp = obj2->getDenseOrTypedArrayElement(JSID_TO_INT(id)); |
|
4641 return true; |
|
4642 } |
|
4643 |
|
4644 /* Special case 'length' on Array and TypedArray. */ |
|
4645 if (IdIsLength(cx, id)) { |
|
4646 if (obj->is<ArrayObject>()) { |
|
4647 vp->setNumber(obj->as<ArrayObject>().length()); |
|
4648 return true; |
|
4649 } |
|
4650 |
|
4651 if (obj->is<TypedArrayObject>()) { |
|
4652 vp->setNumber(obj->as<TypedArrayObject>().length()); |
|
4653 return true; |
|
4654 } |
|
4655 } |
|
4656 |
|
4657 return NativeGetPureInline(obj2, shape, vp); |
|
4658 } |
|
4659 |
|
4660 static bool |
|
4661 MOZ_ALWAYS_INLINE |
|
4662 GetElementPure(ThreadSafeContext *cx, JSObject *obj, uint32_t index, Value *vp) |
|
4663 { |
|
4664 if (index <= JSID_INT_MAX) |
|
4665 return GetPropertyPure(cx, obj, INT_TO_JSID(index), vp); |
|
4666 return false; |
|
4667 } |
|
4668 |
|
4669 /* |
|
4670 * A pure version of GetObjectElementOperation that can be called from |
|
4671 * parallel code without locking. This variant returns false whenever a |
|
4672 * side-effect might have occurred. |
|
4673 */ |
|
4674 bool |
|
4675 js::GetObjectElementOperationPure(ThreadSafeContext *cx, JSObject *obj, const Value &prop, |
|
4676 Value *vp) |
|
4677 { |
|
4678 uint32_t index; |
|
4679 if (IsDefinitelyIndex(prop, &index)) |
|
4680 return GetElementPure(cx, obj, index, vp); |
|
4681 |
|
4682 /* Atomizing the property value is effectful and not threadsafe. */ |
|
4683 if (!prop.isString() || !prop.toString()->isAtom()) |
|
4684 return false; |
|
4685 |
|
4686 JSAtom *name = &prop.toString()->asAtom(); |
|
4687 if (name->isIndex(&index)) |
|
4688 return GetElementPure(cx, obj, index, vp); |
|
4689 |
|
4690 return GetPropertyPure(cx, obj, NameToId(name->asPropertyName()), vp); |
|
4691 } |
|
4692 |
|
4693 bool |
|
4694 baseops::GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, |
|
4695 MutableHandleValue vp) |
|
4696 { |
|
4697 RootedId id(cx); |
|
4698 if (!IndexToId(cx, index, &id)) |
|
4699 return false; |
|
4700 |
|
4701 /* This call site is hot -- use the always-inlined variant of js_GetPropertyHelper(). */ |
|
4702 return GetPropertyHelperInline<CanGC>(cx, obj, receiver, id, vp); |
|
4703 } |
|
4704 |
|
4705 static bool |
|
4706 MaybeReportUndeclaredVarAssignment(JSContext *cx, JSString *propname) |
|
4707 { |
|
4708 { |
|
4709 JSScript *script = cx->currentScript(nullptr, JSContext::ALLOW_CROSS_COMPARTMENT); |
|
4710 if (!script) |
|
4711 return true; |
|
4712 |
|
4713 // If the code is not strict and extra warnings aren't enabled, then no |
|
4714 // check is needed. |
|
4715 if (!script->strict() && !cx->options().extraWarnings()) |
|
4716 return true; |
|
4717 } |
|
4718 |
|
4719 JSAutoByteString bytes(cx, propname); |
|
4720 return !!bytes && |
|
4721 JS_ReportErrorFlagsAndNumber(cx, |
|
4722 (JSREPORT_WARNING | JSREPORT_STRICT |
|
4723 | JSREPORT_STRICT_MODE_ERROR), |
|
4724 js_GetErrorMessage, nullptr, |
|
4725 JSMSG_UNDECLARED_VAR, bytes.ptr()); |
|
4726 } |
|
4727 |
|
4728 bool |
|
4729 js::ReportIfUndeclaredVarAssignment(JSContext *cx, HandleString propname) |
|
4730 { |
|
4731 { |
|
4732 jsbytecode *pc; |
|
4733 JSScript *script = cx->currentScript(&pc, JSContext::ALLOW_CROSS_COMPARTMENT); |
|
4734 if (!script) |
|
4735 return true; |
|
4736 |
|
4737 // If the code is not strict and extra warnings aren't enabled, then no |
|
4738 // check is needed. |
|
4739 if (!script->strict() && !cx->options().extraWarnings()) |
|
4740 return true; |
|
4741 |
|
4742 /* |
|
4743 * We only need to check for bare name mutations: we shouldn't be |
|
4744 * warning, or throwing, or whatever, if we're not doing a variable |
|
4745 * access. |
|
4746 * |
|
4747 * TryConvertToGname in frontend/BytecodeEmitter.cpp checks for rather |
|
4748 * more opcodes when it does, in the normal course of events, what this |
|
4749 * method does in the abnormal course of events. Because we're called |
|
4750 * in narrower circumstances, we only need check two. We don't need to |
|
4751 * check for the increment/decrement opcodes because they're no-ops: |
|
4752 * the actual semantics are implemented by desugaring. And we don't |
|
4753 * need to check name-access because this method is only supposed to be |
|
4754 * called in assignment contexts. |
|
4755 */ |
|
4756 MOZ_ASSERT(*pc != JSOP_NAME); |
|
4757 MOZ_ASSERT(*pc != JSOP_GETGNAME); |
|
4758 if (*pc != JSOP_SETNAME && *pc != JSOP_SETGNAME) |
|
4759 return true; |
|
4760 } |
|
4761 |
|
4762 JSAutoByteString bytes(cx, propname); |
|
4763 return !!bytes && |
|
4764 JS_ReportErrorFlagsAndNumber(cx, |
|
4765 JSREPORT_WARNING | JSREPORT_STRICT | |
|
4766 JSREPORT_STRICT_MODE_ERROR, |
|
4767 js_GetErrorMessage, nullptr, |
|
4768 JSMSG_UNDECLARED_VAR, bytes.ptr()); |
|
4769 } |
|
4770 |
|
4771 bool |
|
4772 JSObject::reportReadOnly(ThreadSafeContext *cxArg, jsid id, unsigned report) |
|
4773 { |
|
4774 if (cxArg->isForkJoinContext()) |
|
4775 return cxArg->asForkJoinContext()->reportError(ParallelBailoutUnsupportedVM, report); |
|
4776 |
|
4777 if (!cxArg->isJSContext()) |
|
4778 return true; |
|
4779 |
|
4780 JSContext *cx = cxArg->asJSContext(); |
|
4781 RootedValue val(cx, IdToValue(id)); |
|
4782 return js_ReportValueErrorFlags(cx, report, JSMSG_READ_ONLY, |
|
4783 JSDVG_IGNORE_STACK, val, js::NullPtr(), |
|
4784 nullptr, nullptr); |
|
4785 } |
|
4786 |
|
4787 bool |
|
4788 JSObject::reportNotConfigurable(ThreadSafeContext *cxArg, jsid id, unsigned report) |
|
4789 { |
|
4790 if (cxArg->isForkJoinContext()) |
|
4791 return cxArg->asForkJoinContext()->reportError(ParallelBailoutUnsupportedVM, report); |
|
4792 |
|
4793 if (!cxArg->isJSContext()) |
|
4794 return true; |
|
4795 |
|
4796 JSContext *cx = cxArg->asJSContext(); |
|
4797 RootedValue val(cx, IdToValue(id)); |
|
4798 return js_ReportValueErrorFlags(cx, report, JSMSG_CANT_DELETE, |
|
4799 JSDVG_IGNORE_STACK, val, js::NullPtr(), |
|
4800 nullptr, nullptr); |
|
4801 } |
|
4802 |
|
4803 bool |
|
4804 JSObject::reportNotExtensible(ThreadSafeContext *cxArg, unsigned report) |
|
4805 { |
|
4806 if (cxArg->isForkJoinContext()) |
|
4807 return cxArg->asForkJoinContext()->reportError(ParallelBailoutUnsupportedVM, report); |
|
4808 |
|
4809 if (!cxArg->isJSContext()) |
|
4810 return true; |
|
4811 |
|
4812 JSContext *cx = cxArg->asJSContext(); |
|
4813 RootedValue val(cx, ObjectValue(*this)); |
|
4814 return js_ReportValueErrorFlags(cx, report, JSMSG_OBJECT_NOT_EXTENSIBLE, |
|
4815 JSDVG_IGNORE_STACK, val, js::NullPtr(), |
|
4816 nullptr, nullptr); |
|
4817 } |
|
4818 |
|
4819 bool |
|
4820 JSObject::callMethod(JSContext *cx, HandleId id, unsigned argc, Value *argv, MutableHandleValue vp) |
|
4821 { |
|
4822 RootedValue fval(cx); |
|
4823 RootedObject obj(cx, this); |
|
4824 if (!JSObject::getGeneric(cx, obj, obj, id, &fval)) |
|
4825 return false; |
|
4826 return Invoke(cx, ObjectValue(*obj), fval, argc, argv, vp); |
|
4827 } |
|
4828 |
|
4829 template <ExecutionMode mode> |
|
4830 bool |
|
4831 baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg, |
|
4832 HandleObject obj, HandleObject receiver, HandleId id, |
|
4833 QualifiedBool qualified, MutableHandleValue vp, bool strict) |
|
4834 { |
|
4835 JS_ASSERT(cxArg->isThreadLocal(obj)); |
|
4836 |
|
4837 if (MOZ_UNLIKELY(obj->watched())) { |
|
4838 if (mode == ParallelExecution) |
|
4839 return false; |
|
4840 |
|
4841 /* Fire watchpoints, if any. */ |
|
4842 JSContext *cx = cxArg->asJSContext(); |
|
4843 WatchpointMap *wpmap = cx->compartment()->watchpointMap; |
|
4844 if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) |
|
4845 return false; |
|
4846 } |
|
4847 |
|
4848 RootedObject pobj(cxArg); |
|
4849 RootedShape shape(cxArg); |
|
4850 if (mode == ParallelExecution) { |
|
4851 if (!LookupPropertyPure(obj, id, pobj.address(), shape.address())) |
|
4852 return false; |
|
4853 } else { |
|
4854 JSContext *cx = cxArg->asJSContext(); |
|
4855 if (!LookupNativeProperty(cx, obj, id, &pobj, &shape)) |
|
4856 return false; |
|
4857 } |
|
4858 if (shape) { |
|
4859 if (!pobj->isNative()) { |
|
4860 if (pobj->is<ProxyObject>()) { |
|
4861 if (mode == ParallelExecution) |
|
4862 return false; |
|
4863 |
|
4864 JSContext *cx = cxArg->asJSContext(); |
|
4865 Rooted<PropertyDescriptor> pd(cx); |
|
4866 if (!Proxy::getPropertyDescriptor(cx, pobj, id, &pd)) |
|
4867 return false; |
|
4868 |
|
4869 if ((pd.attributes() & (JSPROP_SHARED | JSPROP_SHADOWABLE)) == JSPROP_SHARED) { |
|
4870 return !pd.setter() || |
|
4871 CallSetter(cx, receiver, id, pd.setter(), pd.attributes(), strict, vp); |
|
4872 } |
|
4873 |
|
4874 if (pd.isReadonly()) { |
|
4875 if (strict) |
|
4876 return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); |
|
4877 if (cx->options().extraWarnings()) |
|
4878 return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); |
|
4879 return true; |
|
4880 } |
|
4881 } |
|
4882 |
|
4883 shape = nullptr; |
|
4884 } |
|
4885 } else { |
|
4886 /* We should never add properties to lexical blocks. */ |
|
4887 JS_ASSERT(!obj->is<BlockObject>()); |
|
4888 |
|
4889 if (obj->is<GlobalObject>() && !qualified) { |
|
4890 if (mode == ParallelExecution) |
|
4891 return false; |
|
4892 |
|
4893 if (!MaybeReportUndeclaredVarAssignment(cxArg->asJSContext(), JSID_TO_STRING(id))) |
|
4894 return false; |
|
4895 } |
|
4896 } |
|
4897 |
|
4898 /* |
|
4899 * Now either shape is null, meaning id was not found in obj or one of its |
|
4900 * prototypes; or shape is non-null, meaning id was found directly in pobj. |
|
4901 */ |
|
4902 unsigned attrs = JSPROP_ENUMERATE; |
|
4903 const Class *clasp = obj->getClass(); |
|
4904 PropertyOp getter = clasp->getProperty; |
|
4905 StrictPropertyOp setter = clasp->setProperty; |
|
4906 |
|
4907 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
4908 /* ES5 8.12.4 [[Put]] step 2, for a dense data property on pobj. */ |
|
4909 if (pobj != obj) |
|
4910 shape = nullptr; |
|
4911 } else if (shape) { |
|
4912 /* ES5 8.12.4 [[Put]] step 2. */ |
|
4913 if (shape->isAccessorDescriptor()) { |
|
4914 if (shape->hasDefaultSetter()) { |
|
4915 /* Bail out of parallel execution if we are strict to throw. */ |
|
4916 if (mode == ParallelExecution) |
|
4917 return !strict; |
|
4918 |
|
4919 return js_ReportGetterOnlyAssignment(cxArg->asJSContext(), strict); |
|
4920 } |
|
4921 } else { |
|
4922 JS_ASSERT(shape->isDataDescriptor()); |
|
4923 |
|
4924 if (!shape->writable()) { |
|
4925 /* |
|
4926 * Error in strict mode code, warn with extra warnings |
|
4927 * options, otherwise do nothing. |
|
4928 * |
|
4929 * Bail out of parallel execution if we are strict to throw. |
|
4930 */ |
|
4931 if (mode == ParallelExecution) |
|
4932 return !strict; |
|
4933 |
|
4934 JSContext *cx = cxArg->asJSContext(); |
|
4935 if (strict) |
|
4936 return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); |
|
4937 if (cx->options().extraWarnings()) |
|
4938 return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); |
|
4939 return true; |
|
4940 } |
|
4941 } |
|
4942 |
|
4943 attrs = shape->attributes(); |
|
4944 if (pobj != obj) { |
|
4945 /* |
|
4946 * We found id in a prototype object: prepare to share or shadow. |
|
4947 */ |
|
4948 if (!shape->shadowable()) { |
|
4949 if (shape->hasDefaultSetter() && !shape->hasGetterValue()) |
|
4950 return true; |
|
4951 |
|
4952 if (mode == ParallelExecution) |
|
4953 return false; |
|
4954 |
|
4955 return shape->set(cxArg->asJSContext(), obj, receiver, strict, vp); |
|
4956 } |
|
4957 |
|
4958 /* |
|
4959 * Preserve attrs except JSPROP_SHARED, getter, and setter when |
|
4960 * shadowing any property that has no slot (is shared). We must |
|
4961 * clear the shared attribute for the shadowing shape so that the |
|
4962 * property in obj that it defines has a slot to retain the value |
|
4963 * being set, in case the setter simply cannot operate on instances |
|
4964 * of obj's class by storing the value in some class-specific |
|
4965 * location. |
|
4966 */ |
|
4967 if (!shape->hasSlot()) { |
|
4968 attrs &= ~JSPROP_SHARED; |
|
4969 getter = shape->getter(); |
|
4970 setter = shape->setter(); |
|
4971 } else { |
|
4972 /* Restore attrs to the ECMA default for new properties. */ |
|
4973 attrs = JSPROP_ENUMERATE; |
|
4974 } |
|
4975 |
|
4976 /* |
|
4977 * Forget we found the proto-property now that we've copied any |
|
4978 * needed member values. |
|
4979 */ |
|
4980 shape = nullptr; |
|
4981 } |
|
4982 } |
|
4983 |
|
4984 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
4985 uint32_t index = JSID_TO_INT(id); |
|
4986 |
|
4987 if (obj->is<TypedArrayObject>()) { |
|
4988 double d; |
|
4989 if (mode == ParallelExecution) { |
|
4990 // Bail if converting the value might invoke user-defined |
|
4991 // conversions. |
|
4992 if (vp.isObject()) |
|
4993 return false; |
|
4994 if (!NonObjectToNumber(cxArg, vp, &d)) |
|
4995 return false; |
|
4996 } else { |
|
4997 if (!ToNumber(cxArg->asJSContext(), vp, &d)) |
|
4998 return false; |
|
4999 } |
|
5000 |
|
5001 // Silently do nothing for out-of-bounds sets, for consistency with |
|
5002 // current behavior. (ES6 currently says to throw for this in |
|
5003 // strict mode code, so we may eventually need to change.) |
|
5004 TypedArrayObject &tarray = obj->as<TypedArrayObject>(); |
|
5005 if (index < tarray.length()) |
|
5006 TypedArrayObject::setElement(tarray, index, d); |
|
5007 return true; |
|
5008 } |
|
5009 |
|
5010 bool definesPast; |
|
5011 if (!WouldDefinePastNonwritableLength(cxArg, obj, index, strict, &definesPast)) |
|
5012 return false; |
|
5013 if (definesPast) { |
|
5014 /* Bail out of parallel execution if we are strict to throw. */ |
|
5015 if (mode == ParallelExecution) |
|
5016 return !strict; |
|
5017 return true; |
|
5018 } |
|
5019 |
|
5020 if (mode == ParallelExecution) |
|
5021 return obj->setDenseElementIfHasType(index, vp); |
|
5022 |
|
5023 obj->setDenseElementWithType(cxArg->asJSContext(), index, vp); |
|
5024 return true; |
|
5025 } |
|
5026 |
|
5027 if (obj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) { |
|
5028 Rooted<ArrayObject*> arr(cxArg, &obj->as<ArrayObject>()); |
|
5029 return ArraySetLength<mode>(cxArg, arr, id, attrs, vp, strict); |
|
5030 } |
|
5031 |
|
5032 if (!shape) { |
|
5033 bool extensible; |
|
5034 if (mode == ParallelExecution) { |
|
5035 if (obj->is<ProxyObject>()) |
|
5036 return false; |
|
5037 extensible = obj->nonProxyIsExtensible(); |
|
5038 } else { |
|
5039 if (!JSObject::isExtensible(cxArg->asJSContext(), obj, &extensible)) |
|
5040 return false; |
|
5041 } |
|
5042 |
|
5043 if (!extensible) { |
|
5044 /* Error in strict mode code, warn with extra warnings option, otherwise do nothing. */ |
|
5045 if (strict) |
|
5046 return obj->reportNotExtensible(cxArg); |
|
5047 if (mode == SequentialExecution && cxArg->asJSContext()->options().extraWarnings()) |
|
5048 return obj->reportNotExtensible(cxArg, JSREPORT_STRICT | JSREPORT_WARNING); |
|
5049 return true; |
|
5050 } |
|
5051 |
|
5052 if (mode == ParallelExecution) { |
|
5053 if (obj->isDelegate()) |
|
5054 return false; |
|
5055 |
|
5056 if (getter != JS_PropertyStub || !HasTypePropertyId(obj, id, vp)) |
|
5057 return false; |
|
5058 } else { |
|
5059 JSContext *cx = cxArg->asJSContext(); |
|
5060 |
|
5061 /* Purge the property cache of now-shadowed id in obj's scope chain. */ |
|
5062 if (!PurgeScopeChain(cx, obj, id)) |
|
5063 return false; |
|
5064 } |
|
5065 |
|
5066 return DefinePropertyOrElement<mode>(cxArg, obj, id, getter, setter, |
|
5067 attrs, vp, true, strict); |
|
5068 } |
|
5069 |
|
5070 return NativeSet<mode>(cxArg, obj, receiver, shape, strict, vp); |
|
5071 } |
|
5072 |
|
5073 template bool |
|
5074 baseops::SetPropertyHelper<SequentialExecution>(JSContext *cx, HandleObject obj, |
|
5075 HandleObject receiver, HandleId id, |
|
5076 QualifiedBool qualified, |
|
5077 MutableHandleValue vp, bool strict); |
|
5078 template bool |
|
5079 baseops::SetPropertyHelper<ParallelExecution>(ForkJoinContext *cx, HandleObject obj, |
|
5080 HandleObject receiver, HandleId id, |
|
5081 QualifiedBool qualified, |
|
5082 MutableHandleValue vp, bool strict); |
|
5083 |
|
5084 bool |
|
5085 baseops::SetElementHelper(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, |
|
5086 MutableHandleValue vp, bool strict) |
|
5087 { |
|
5088 RootedId id(cx); |
|
5089 if (!IndexToId(cx, index, &id)) |
|
5090 return false; |
|
5091 return baseops::SetPropertyHelper<SequentialExecution>(cx, obj, receiver, id, Qualified, vp, |
|
5092 strict); |
|
5093 } |
|
5094 |
|
5095 bool |
|
5096 baseops::GetAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
|
5097 { |
|
5098 RootedObject nobj(cx); |
|
5099 RootedShape shape(cx); |
|
5100 if (!baseops::LookupProperty<CanGC>(cx, obj, id, &nobj, &shape)) |
|
5101 return false; |
|
5102 if (!shape) { |
|
5103 *attrsp = 0; |
|
5104 return true; |
|
5105 } |
|
5106 if (!nobj->isNative()) |
|
5107 return JSObject::getGenericAttributes(cx, nobj, id, attrsp); |
|
5108 |
|
5109 *attrsp = GetShapeAttributes(nobj, shape); |
|
5110 return true; |
|
5111 } |
|
5112 |
|
5113 bool |
|
5114 baseops::SetAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
|
5115 { |
|
5116 RootedObject nobj(cx); |
|
5117 RootedShape shape(cx); |
|
5118 if (!baseops::LookupProperty<CanGC>(cx, obj, id, &nobj, &shape)) |
|
5119 return false; |
|
5120 if (!shape) |
|
5121 return true; |
|
5122 if (nobj->isNative() && IsImplicitDenseOrTypedArrayElement(shape)) { |
|
5123 if (nobj->is<TypedArrayObject>()) { |
|
5124 if (*attrsp == (JSPROP_ENUMERATE | JSPROP_PERMANENT)) |
|
5125 return true; |
|
5126 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_ARRAY_ATTRS); |
|
5127 return false; |
|
5128 } |
|
5129 if (!JSObject::sparsifyDenseElement(cx, nobj, JSID_TO_INT(id))) |
|
5130 return false; |
|
5131 shape = obj->nativeLookup(cx, id); |
|
5132 } |
|
5133 if (nobj->isNative()) { |
|
5134 if (!JSObject::changePropertyAttributes(cx, nobj, shape, *attrsp)) |
|
5135 return false; |
|
5136 if (*attrsp & JSPROP_READONLY) |
|
5137 MarkTypePropertyNonWritable(cx, obj, id); |
|
5138 return true; |
|
5139 } else { |
|
5140 return JSObject::setGenericAttributes(cx, nobj, id, attrsp); |
|
5141 } |
|
5142 } |
|
5143 |
|
5144 bool |
|
5145 baseops::DeleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded) |
|
5146 { |
|
5147 RootedObject proto(cx); |
|
5148 RootedShape shape(cx); |
|
5149 if (!baseops::LookupProperty<CanGC>(cx, obj, id, &proto, &shape)) |
|
5150 return false; |
|
5151 if (!shape || proto != obj) { |
|
5152 /* |
|
5153 * If no property, or the property comes from a prototype, call the |
|
5154 * class's delProperty hook, passing succeeded as the result parameter. |
|
5155 */ |
|
5156 return CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded); |
|
5157 } |
|
5158 |
|
5159 GCPoke(cx->runtime()); |
|
5160 |
|
5161 if (IsImplicitDenseOrTypedArrayElement(shape)) { |
|
5162 if (obj->is<TypedArrayObject>()) { |
|
5163 // Don't delete elements from typed arrays. |
|
5164 *succeeded = false; |
|
5165 return true; |
|
5166 } |
|
5167 |
|
5168 if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded)) |
|
5169 return false; |
|
5170 if (!succeeded) |
|
5171 return true; |
|
5172 |
|
5173 obj->setDenseElementHole(cx, JSID_TO_INT(id)); |
|
5174 return js_SuppressDeletedProperty(cx, obj, id); |
|
5175 } |
|
5176 |
|
5177 if (!shape->configurable()) { |
|
5178 *succeeded = false; |
|
5179 return true; |
|
5180 } |
|
5181 |
|
5182 RootedId propid(cx, shape->propid()); |
|
5183 if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, propid, succeeded)) |
|
5184 return false; |
|
5185 if (!succeeded) |
|
5186 return true; |
|
5187 |
|
5188 return obj->removeProperty(cx, id) && js_SuppressDeletedProperty(cx, obj, id); |
|
5189 } |
|
5190 |
|
5191 bool |
|
5192 baseops::DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
|
5193 bool *succeeded) |
|
5194 { |
|
5195 Rooted<jsid> id(cx, NameToId(name)); |
|
5196 return baseops::DeleteGeneric(cx, obj, id, succeeded); |
|
5197 } |
|
5198 |
|
5199 bool |
|
5200 baseops::DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, bool *succeeded) |
|
5201 { |
|
5202 RootedId id(cx); |
|
5203 if (!IndexToId(cx, index, &id)) |
|
5204 return false; |
|
5205 return baseops::DeleteGeneric(cx, obj, id, succeeded); |
|
5206 } |
|
5207 |
|
5208 bool |
|
5209 js::WatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable) |
|
5210 { |
|
5211 RootedObject obj(cx, GetInnerObject(cx, origObj)); |
|
5212 if (obj->isNative()) { |
|
5213 // Use sparse indexes for watched objects, as dense elements can be |
|
5214 // written to without checking the watchpoint map. |
|
5215 if (!JSObject::sparsifyDenseElements(cx, obj)) |
|
5216 return false; |
|
5217 |
|
5218 types::MarkTypePropertyNonData(cx, obj, id); |
|
5219 } |
|
5220 |
|
5221 WatchpointMap *wpmap = cx->compartment()->watchpointMap; |
|
5222 if (!wpmap) { |
|
5223 wpmap = cx->runtime()->new_<WatchpointMap>(); |
|
5224 if (!wpmap || !wpmap->init()) { |
|
5225 js_ReportOutOfMemory(cx); |
|
5226 return false; |
|
5227 } |
|
5228 cx->compartment()->watchpointMap = wpmap; |
|
5229 } |
|
5230 |
|
5231 return wpmap->watch(cx, obj, id, js::WatchHandler, callable); |
|
5232 } |
|
5233 |
|
5234 bool |
|
5235 baseops::Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable) |
|
5236 { |
|
5237 if (!obj->isNative() || obj->is<TypedArrayObject>()) { |
|
5238 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_WATCH, |
|
5239 obj->getClass()->name); |
|
5240 return false; |
|
5241 } |
|
5242 |
|
5243 return WatchGuts(cx, obj, id, callable); |
|
5244 } |
|
5245 |
|
5246 bool |
|
5247 js::UnwatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id) |
|
5248 { |
|
5249 // Looking in the map for an unsupported object will never hit, so we don't |
|
5250 // need to check for nativeness or watchable-ness here. |
|
5251 RootedObject obj(cx, GetInnerObject(cx, origObj)); |
|
5252 if (WatchpointMap *wpmap = cx->compartment()->watchpointMap) |
|
5253 wpmap->unwatch(obj, id, nullptr, nullptr); |
|
5254 return true; |
|
5255 } |
|
5256 |
|
5257 bool |
|
5258 baseops::Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id) |
|
5259 { |
|
5260 return UnwatchGuts(cx, obj, id); |
|
5261 } |
|
5262 |
|
5263 bool |
|
5264 js::HasDataProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp) |
|
5265 { |
|
5266 if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { |
|
5267 *vp = obj->getDenseElement(JSID_TO_INT(id)); |
|
5268 return true; |
|
5269 } |
|
5270 |
|
5271 if (Shape *shape = obj->nativeLookup(cx, id)) { |
|
5272 if (shape->hasDefaultGetter() && shape->hasSlot()) { |
|
5273 *vp = obj->nativeGetSlot(shape->slot()); |
|
5274 return true; |
|
5275 } |
|
5276 } |
|
5277 |
|
5278 return false; |
|
5279 } |
|
5280 |
|
5281 /* |
|
5282 * Gets |obj[id]|. If that value's not callable, returns true and stores a |
|
5283 * non-primitive value in *vp. If it's callable, calls it with no arguments |
|
5284 * and |obj| as |this|, returning the result in *vp. |
|
5285 * |
|
5286 * This is a mini-abstraction for ES5 8.12.8 [[DefaultValue]], either steps 1-2 |
|
5287 * or steps 3-4. |
|
5288 */ |
|
5289 static bool |
|
5290 MaybeCallMethod(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) |
|
5291 { |
|
5292 if (!JSObject::getGeneric(cx, obj, obj, id, vp)) |
|
5293 return false; |
|
5294 if (!js_IsCallable(vp)) { |
|
5295 vp.setObject(*obj); |
|
5296 return true; |
|
5297 } |
|
5298 return Invoke(cx, ObjectValue(*obj), vp, 0, nullptr, vp); |
|
5299 } |
|
5300 |
|
5301 JS_FRIEND_API(bool) |
|
5302 js::DefaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp) |
|
5303 { |
|
5304 JS_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID); |
|
5305 |
|
5306 Rooted<jsid> id(cx); |
|
5307 |
|
5308 const Class *clasp = obj->getClass(); |
|
5309 if (hint == JSTYPE_STRING) { |
|
5310 id = NameToId(cx->names().toString); |
|
5311 |
|
5312 /* Optimize (new String(...)).toString(). */ |
|
5313 if (clasp == &StringObject::class_) { |
|
5314 if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) { |
|
5315 vp.setString(obj->as<StringObject>().unbox()); |
|
5316 return true; |
|
5317 } |
|
5318 } |
|
5319 |
|
5320 if (!MaybeCallMethod(cx, obj, id, vp)) |
|
5321 return false; |
|
5322 if (vp.isPrimitive()) |
|
5323 return true; |
|
5324 |
|
5325 id = NameToId(cx->names().valueOf); |
|
5326 if (!MaybeCallMethod(cx, obj, id, vp)) |
|
5327 return false; |
|
5328 if (vp.isPrimitive()) |
|
5329 return true; |
|
5330 } else { |
|
5331 |
|
5332 /* Optimize new String(...).valueOf(). */ |
|
5333 if (clasp == &StringObject::class_) { |
|
5334 id = NameToId(cx->names().valueOf); |
|
5335 if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) { |
|
5336 vp.setString(obj->as<StringObject>().unbox()); |
|
5337 return true; |
|
5338 } |
|
5339 } |
|
5340 |
|
5341 /* Optimize new Number(...).valueOf(). */ |
|
5342 if (clasp == &NumberObject::class_) { |
|
5343 id = NameToId(cx->names().valueOf); |
|
5344 if (ClassMethodIsNative(cx, obj, &NumberObject::class_, id, js_num_valueOf)) { |
|
5345 vp.setNumber(obj->as<NumberObject>().unbox()); |
|
5346 return true; |
|
5347 } |
|
5348 } |
|
5349 |
|
5350 id = NameToId(cx->names().valueOf); |
|
5351 if (!MaybeCallMethod(cx, obj, id, vp)) |
|
5352 return false; |
|
5353 if (vp.isPrimitive()) |
|
5354 return true; |
|
5355 |
|
5356 id = NameToId(cx->names().toString); |
|
5357 if (!MaybeCallMethod(cx, obj, id, vp)) |
|
5358 return false; |
|
5359 if (vp.isPrimitive()) |
|
5360 return true; |
|
5361 } |
|
5362 |
|
5363 /* Avoid recursive death when decompiling in js_ReportValueError. */ |
|
5364 RootedString str(cx); |
|
5365 if (hint == JSTYPE_STRING) { |
|
5366 str = JS_InternString(cx, clasp->name); |
|
5367 if (!str) |
|
5368 return false; |
|
5369 } else { |
|
5370 str = nullptr; |
|
5371 } |
|
5372 |
|
5373 RootedValue val(cx, ObjectValue(*obj)); |
|
5374 js_ReportValueError2(cx, JSMSG_CANT_CONVERT_TO, JSDVG_SEARCH_STACK, val, str, |
|
5375 (hint == JSTYPE_VOID) ? "primitive type" : TypeStrings[hint]); |
|
5376 return false; |
|
5377 } |
|
5378 |
|
5379 JS_FRIEND_API(bool) |
|
5380 JS_EnumerateState(JSContext *cx, HandleObject obj, JSIterateOp enum_op, |
|
5381 MutableHandleValue statep, JS::MutableHandleId idp) |
|
5382 { |
|
5383 /* If the class has a custom JSCLASS_NEW_ENUMERATE hook, call it. */ |
|
5384 const Class *clasp = obj->getClass(); |
|
5385 JSEnumerateOp enumerate = clasp->enumerate; |
|
5386 if (clasp->flags & JSCLASS_NEW_ENUMERATE) { |
|
5387 JS_ASSERT(enumerate != JS_EnumerateStub); |
|
5388 return ((JSNewEnumerateOp) enumerate)(cx, obj, enum_op, statep, idp); |
|
5389 } |
|
5390 |
|
5391 if (!enumerate(cx, obj)) |
|
5392 return false; |
|
5393 |
|
5394 /* Tell InitNativeIterator to treat us like a native object. */ |
|
5395 JS_ASSERT(enum_op == JSENUMERATE_INIT || enum_op == JSENUMERATE_INIT_ALL); |
|
5396 statep.setMagic(JS_NATIVE_ENUMERATE); |
|
5397 return true; |
|
5398 } |
|
5399 |
|
5400 bool |
|
5401 js::IsDelegate(JSContext *cx, HandleObject obj, const js::Value &v, bool *result) |
|
5402 { |
|
5403 if (v.isPrimitive()) { |
|
5404 *result = false; |
|
5405 return true; |
|
5406 } |
|
5407 return IsDelegateOfObject(cx, obj, &v.toObject(), result); |
|
5408 } |
|
5409 |
|
5410 bool |
|
5411 js::IsDelegateOfObject(JSContext *cx, HandleObject protoObj, JSObject* obj, bool *result) |
|
5412 { |
|
5413 RootedObject obj2(cx, obj); |
|
5414 for (;;) { |
|
5415 if (!JSObject::getProto(cx, obj2, &obj2)) |
|
5416 return false; |
|
5417 if (!obj2) { |
|
5418 *result = false; |
|
5419 return true; |
|
5420 } |
|
5421 if (obj2 == protoObj) { |
|
5422 *result = true; |
|
5423 return true; |
|
5424 } |
|
5425 } |
|
5426 } |
|
5427 |
|
5428 JSObject * |
|
5429 js::GetBuiltinPrototypePure(GlobalObject *global, JSProtoKey protoKey) |
|
5430 { |
|
5431 JS_ASSERT(JSProto_Null <= protoKey); |
|
5432 JS_ASSERT(protoKey < JSProto_LIMIT); |
|
5433 |
|
5434 if (protoKey != JSProto_Null) { |
|
5435 const Value &v = global->getPrototype(protoKey); |
|
5436 if (v.isObject()) |
|
5437 return &v.toObject(); |
|
5438 } |
|
5439 |
|
5440 return nullptr; |
|
5441 } |
|
5442 |
|
5443 /* |
|
5444 * The first part of this function has been hand-expanded and optimized into |
|
5445 * NewBuiltinClassInstance in jsobjinlines.h. |
|
5446 */ |
|
5447 bool |
|
5448 js::FindClassPrototype(ExclusiveContext *cx, MutableHandleObject protop, const Class *clasp) |
|
5449 { |
|
5450 protop.set(nullptr); |
|
5451 JSProtoKey protoKey = GetClassProtoKey(clasp); |
|
5452 if (protoKey != JSProto_Null) |
|
5453 return GetBuiltinPrototype(cx, protoKey, protop); |
|
5454 |
|
5455 RootedObject ctor(cx); |
|
5456 if (!FindClassObject(cx, &ctor, clasp)) |
|
5457 return false; |
|
5458 |
|
5459 if (ctor && ctor->is<JSFunction>()) { |
|
5460 RootedValue v(cx); |
|
5461 if (cx->isJSContext()) { |
|
5462 if (!JSObject::getProperty(cx->asJSContext(), |
|
5463 ctor, ctor, cx->names().prototype, &v)) |
|
5464 { |
|
5465 return false; |
|
5466 } |
|
5467 } else { |
|
5468 Shape *shape = ctor->nativeLookup(cx, cx->names().prototype); |
|
5469 if (!shape || !NativeGetPureInline(ctor, shape, v.address())) |
|
5470 return false; |
|
5471 } |
|
5472 if (v.isObject()) |
|
5473 protop.set(&v.toObject()); |
|
5474 } |
|
5475 return true; |
|
5476 } |
|
5477 |
|
5478 JSObject * |
|
5479 js::PrimitiveToObject(JSContext *cx, const Value &v) |
|
5480 { |
|
5481 if (v.isString()) { |
|
5482 Rooted<JSString*> str(cx, v.toString()); |
|
5483 return StringObject::create(cx, str); |
|
5484 } |
|
5485 if (v.isNumber()) |
|
5486 return NumberObject::create(cx, v.toNumber()); |
|
5487 |
|
5488 JS_ASSERT(v.isBoolean()); |
|
5489 return BooleanObject::create(cx, v.toBoolean()); |
|
5490 } |
|
5491 |
|
5492 /* Callers must handle the already-object case . */ |
|
5493 JSObject * |
|
5494 js::ToObjectSlow(JSContext *cx, HandleValue val, bool reportScanStack) |
|
5495 { |
|
5496 JS_ASSERT(!val.isMagic()); |
|
5497 JS_ASSERT(!val.isObject()); |
|
5498 |
|
5499 if (val.isNullOrUndefined()) { |
|
5500 if (reportScanStack) { |
|
5501 js_ReportIsNullOrUndefined(cx, JSDVG_SEARCH_STACK, val, NullPtr()); |
|
5502 } else { |
|
5503 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, |
|
5504 val.isNull() ? "null" : "undefined", "object"); |
|
5505 } |
|
5506 return nullptr; |
|
5507 } |
|
5508 |
|
5509 return PrimitiveToObject(cx, val); |
|
5510 } |
|
5511 |
|
5512 void |
|
5513 js_GetObjectSlotName(JSTracer *trc, char *buf, size_t bufsize) |
|
5514 { |
|
5515 JS_ASSERT(trc->debugPrinter() == js_GetObjectSlotName); |
|
5516 |
|
5517 JSObject *obj = (JSObject *)trc->debugPrintArg(); |
|
5518 uint32_t slot = uint32_t(trc->debugPrintIndex()); |
|
5519 |
|
5520 Shape *shape; |
|
5521 if (obj->isNative()) { |
|
5522 shape = obj->lastProperty(); |
|
5523 while (shape && (!shape->hasSlot() || shape->slot() != slot)) |
|
5524 shape = shape->previous(); |
|
5525 } else { |
|
5526 shape = nullptr; |
|
5527 } |
|
5528 |
|
5529 if (!shape) { |
|
5530 const char *slotname = nullptr; |
|
5531 if (obj->is<GlobalObject>()) { |
|
5532 #define TEST_SLOT_MATCHES_PROTOTYPE(name,code,init,clasp) \ |
|
5533 if ((code) == slot) { slotname = js_##name##_str; goto found; } |
|
5534 JS_FOR_EACH_PROTOTYPE(TEST_SLOT_MATCHES_PROTOTYPE) |
|
5535 #undef TEST_SLOT_MATCHES_PROTOTYPE |
|
5536 } |
|
5537 found: |
|
5538 if (slotname) |
|
5539 JS_snprintf(buf, bufsize, "CLASS_OBJECT(%s)", slotname); |
|
5540 else |
|
5541 JS_snprintf(buf, bufsize, "**UNKNOWN SLOT %ld**", (long)slot); |
|
5542 } else { |
|
5543 jsid propid = shape->propid(); |
|
5544 if (JSID_IS_INT(propid)) { |
|
5545 JS_snprintf(buf, bufsize, "%ld", (long)JSID_TO_INT(propid)); |
|
5546 } else if (JSID_IS_ATOM(propid)) { |
|
5547 PutEscapedString(buf, bufsize, JSID_TO_ATOM(propid), 0); |
|
5548 } else { |
|
5549 JS_snprintf(buf, bufsize, "**FINALIZED ATOM KEY**"); |
|
5550 } |
|
5551 } |
|
5552 } |
|
5553 |
|
5554 bool |
|
5555 js_ReportGetterOnlyAssignment(JSContext *cx, bool strict) |
|
5556 { |
|
5557 return JS_ReportErrorFlagsAndNumber(cx, |
|
5558 strict |
|
5559 ? JSREPORT_ERROR |
|
5560 : JSREPORT_WARNING | JSREPORT_STRICT, |
|
5561 js_GetErrorMessage, nullptr, |
|
5562 JSMSG_GETTER_ONLY); |
|
5563 } |
|
5564 |
|
5565 JS_FRIEND_API(bool) |
|
5566 js_GetterOnlyPropertyStub(JSContext *cx, HandleObject obj, HandleId id, bool strict, |
|
5567 MutableHandleValue vp) |
|
5568 { |
|
5569 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_GETTER_ONLY); |
|
5570 return false; |
|
5571 } |
|
5572 |
|
5573 #ifdef DEBUG |
|
5574 |
|
5575 /* |
|
5576 * Routines to print out values during debugging. These are FRIEND_API to help |
|
5577 * the debugger find them and to support temporarily hacking js_Dump* calls |
|
5578 * into other code. |
|
5579 */ |
|
5580 |
|
5581 static void |
|
5582 dumpValue(const Value &v) |
|
5583 { |
|
5584 if (v.isNull()) |
|
5585 fprintf(stderr, "null"); |
|
5586 else if (v.isUndefined()) |
|
5587 fprintf(stderr, "undefined"); |
|
5588 else if (v.isInt32()) |
|
5589 fprintf(stderr, "%d", v.toInt32()); |
|
5590 else if (v.isDouble()) |
|
5591 fprintf(stderr, "%g", v.toDouble()); |
|
5592 else if (v.isString()) |
|
5593 v.toString()->dump(); |
|
5594 else if (v.isObject() && v.toObject().is<JSFunction>()) { |
|
5595 JSFunction *fun = &v.toObject().as<JSFunction>(); |
|
5596 if (fun->displayAtom()) { |
|
5597 fputs("<function ", stderr); |
|
5598 FileEscapedString(stderr, fun->displayAtom(), 0); |
|
5599 } else { |
|
5600 fputs("<unnamed function", stderr); |
|
5601 } |
|
5602 if (fun->hasScript()) { |
|
5603 JSScript *script = fun->nonLazyScript(); |
|
5604 fprintf(stderr, " (%s:%d)", |
|
5605 script->filename() ? script->filename() : "", (int) script->lineno()); |
|
5606 } |
|
5607 fprintf(stderr, " at %p>", (void *) fun); |
|
5608 } else if (v.isObject()) { |
|
5609 JSObject *obj = &v.toObject(); |
|
5610 const Class *clasp = obj->getClass(); |
|
5611 fprintf(stderr, "<%s%s at %p>", |
|
5612 clasp->name, |
|
5613 (clasp == &JSObject::class_) ? "" : " object", |
|
5614 (void *) obj); |
|
5615 } else if (v.isBoolean()) { |
|
5616 if (v.toBoolean()) |
|
5617 fprintf(stderr, "true"); |
|
5618 else |
|
5619 fprintf(stderr, "false"); |
|
5620 } else if (v.isMagic()) { |
|
5621 fprintf(stderr, "<invalid"); |
|
5622 #ifdef DEBUG |
|
5623 switch (v.whyMagic()) { |
|
5624 case JS_ELEMENTS_HOLE: fprintf(stderr, " elements hole"); break; |
|
5625 case JS_NATIVE_ENUMERATE: fprintf(stderr, " native enumeration"); break; |
|
5626 case JS_NO_ITER_VALUE: fprintf(stderr, " no iter value"); break; |
|
5627 case JS_GENERATOR_CLOSING: fprintf(stderr, " generator closing"); break; |
|
5628 case JS_OPTIMIZED_OUT: fprintf(stderr, " optimized out"); break; |
|
5629 default: fprintf(stderr, " ?!"); break; |
|
5630 } |
|
5631 #endif |
|
5632 fprintf(stderr, ">"); |
|
5633 } else { |
|
5634 fprintf(stderr, "unexpected value"); |
|
5635 } |
|
5636 } |
|
5637 |
|
5638 JS_FRIEND_API(void) |
|
5639 js_DumpValue(const Value &val) |
|
5640 { |
|
5641 dumpValue(val); |
|
5642 fputc('\n', stderr); |
|
5643 } |
|
5644 |
|
5645 JS_FRIEND_API(void) |
|
5646 js_DumpId(jsid id) |
|
5647 { |
|
5648 fprintf(stderr, "jsid %p = ", (void *) JSID_BITS(id)); |
|
5649 dumpValue(IdToValue(id)); |
|
5650 fputc('\n', stderr); |
|
5651 } |
|
5652 |
|
5653 static void |
|
5654 DumpProperty(JSObject *obj, Shape &shape) |
|
5655 { |
|
5656 jsid id = shape.propid(); |
|
5657 uint8_t attrs = shape.attributes(); |
|
5658 |
|
5659 fprintf(stderr, " ((JSShape *) %p) ", (void *) &shape); |
|
5660 if (attrs & JSPROP_ENUMERATE) fprintf(stderr, "enumerate "); |
|
5661 if (attrs & JSPROP_READONLY) fprintf(stderr, "readonly "); |
|
5662 if (attrs & JSPROP_PERMANENT) fprintf(stderr, "permanent "); |
|
5663 if (attrs & JSPROP_SHARED) fprintf(stderr, "shared "); |
|
5664 |
|
5665 if (shape.hasGetterValue()) |
|
5666 fprintf(stderr, "getterValue=%p ", (void *) shape.getterObject()); |
|
5667 else if (!shape.hasDefaultGetter()) |
|
5668 fprintf(stderr, "getterOp=%p ", JS_FUNC_TO_DATA_PTR(void *, shape.getterOp())); |
|
5669 |
|
5670 if (shape.hasSetterValue()) |
|
5671 fprintf(stderr, "setterValue=%p ", (void *) shape.setterObject()); |
|
5672 else if (!shape.hasDefaultSetter()) |
|
5673 fprintf(stderr, "setterOp=%p ", JS_FUNC_TO_DATA_PTR(void *, shape.setterOp())); |
|
5674 |
|
5675 if (JSID_IS_ATOM(id)) |
|
5676 JSID_TO_STRING(id)->dump(); |
|
5677 else if (JSID_IS_INT(id)) |
|
5678 fprintf(stderr, "%d", (int) JSID_TO_INT(id)); |
|
5679 else |
|
5680 fprintf(stderr, "unknown jsid %p", (void *) JSID_BITS(id)); |
|
5681 |
|
5682 uint32_t slot = shape.hasSlot() ? shape.maybeSlot() : SHAPE_INVALID_SLOT; |
|
5683 fprintf(stderr, ": slot %d", slot); |
|
5684 if (shape.hasSlot()) { |
|
5685 fprintf(stderr, " = "); |
|
5686 dumpValue(obj->getSlot(slot)); |
|
5687 } else if (slot != SHAPE_INVALID_SLOT) { |
|
5688 fprintf(stderr, " (INVALID!)"); |
|
5689 } |
|
5690 fprintf(stderr, "\n"); |
|
5691 } |
|
5692 |
|
5693 bool |
|
5694 JSObject::uninlinedIsProxy() const |
|
5695 { |
|
5696 return is<ProxyObject>(); |
|
5697 } |
|
5698 |
|
5699 void |
|
5700 JSObject::dump() |
|
5701 { |
|
5702 JSObject *obj = this; |
|
5703 fprintf(stderr, "object %p\n", (void *) obj); |
|
5704 const Class *clasp = obj->getClass(); |
|
5705 fprintf(stderr, "class %p %s\n", (const void *)clasp, clasp->name); |
|
5706 |
|
5707 fprintf(stderr, "flags:"); |
|
5708 if (obj->isDelegate()) fprintf(stderr, " delegate"); |
|
5709 if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible"); |
|
5710 if (obj->isIndexed()) fprintf(stderr, " indexed"); |
|
5711 if (obj->isBoundFunction()) fprintf(stderr, " bound_function"); |
|
5712 if (obj->isVarObj()) fprintf(stderr, " varobj"); |
|
5713 if (obj->watched()) fprintf(stderr, " watched"); |
|
5714 if (obj->isIteratedSingleton()) fprintf(stderr, " iterated_singleton"); |
|
5715 if (obj->isNewTypeUnknown()) fprintf(stderr, " new_type_unknown"); |
|
5716 if (obj->hasUncacheableProto()) fprintf(stderr, " has_uncacheable_proto"); |
|
5717 if (obj->hadElementsAccess()) fprintf(stderr, " had_elements_access"); |
|
5718 |
|
5719 if (obj->isNative()) { |
|
5720 if (obj->inDictionaryMode()) |
|
5721 fprintf(stderr, " inDictionaryMode"); |
|
5722 if (obj->hasShapeTable()) |
|
5723 fprintf(stderr, " hasShapeTable"); |
|
5724 } |
|
5725 fprintf(stderr, "\n"); |
|
5726 |
|
5727 if (obj->isNative()) { |
|
5728 uint32_t slots = obj->getDenseInitializedLength(); |
|
5729 if (slots) { |
|
5730 fprintf(stderr, "elements\n"); |
|
5731 for (uint32_t i = 0; i < slots; i++) { |
|
5732 fprintf(stderr, " %3d: ", i); |
|
5733 dumpValue(obj->getDenseElement(i)); |
|
5734 fprintf(stderr, "\n"); |
|
5735 fflush(stderr); |
|
5736 } |
|
5737 } |
|
5738 } |
|
5739 |
|
5740 fprintf(stderr, "proto "); |
|
5741 TaggedProto proto = obj->getTaggedProto(); |
|
5742 if (proto.isLazy()) |
|
5743 fprintf(stderr, "<lazy>"); |
|
5744 else |
|
5745 dumpValue(ObjectOrNullValue(proto.toObjectOrNull())); |
|
5746 fputc('\n', stderr); |
|
5747 |
|
5748 fprintf(stderr, "parent "); |
|
5749 dumpValue(ObjectOrNullValue(obj->getParent())); |
|
5750 fputc('\n', stderr); |
|
5751 |
|
5752 if (clasp->flags & JSCLASS_HAS_PRIVATE) |
|
5753 fprintf(stderr, "private %p\n", obj->getPrivate()); |
|
5754 |
|
5755 if (!obj->isNative()) |
|
5756 fprintf(stderr, "not native\n"); |
|
5757 |
|
5758 uint32_t reservedEnd = JSCLASS_RESERVED_SLOTS(clasp); |
|
5759 uint32_t slots = obj->slotSpan(); |
|
5760 uint32_t stop = obj->isNative() ? reservedEnd : slots; |
|
5761 if (stop > 0) |
|
5762 fprintf(stderr, obj->isNative() ? "reserved slots:\n" : "slots:\n"); |
|
5763 for (uint32_t i = 0; i < stop; i++) { |
|
5764 fprintf(stderr, " %3d ", i); |
|
5765 if (i < reservedEnd) |
|
5766 fprintf(stderr, "(reserved) "); |
|
5767 fprintf(stderr, "= "); |
|
5768 dumpValue(obj->getSlot(i)); |
|
5769 fputc('\n', stderr); |
|
5770 } |
|
5771 |
|
5772 if (obj->isNative()) { |
|
5773 fprintf(stderr, "properties:\n"); |
|
5774 Vector<Shape *, 8, SystemAllocPolicy> props; |
|
5775 for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) |
|
5776 props.append(&r.front()); |
|
5777 for (size_t i = props.length(); i-- != 0;) |
|
5778 DumpProperty(obj, *props[i]); |
|
5779 } |
|
5780 fputc('\n', stderr); |
|
5781 } |
|
5782 |
|
5783 static void |
|
5784 MaybeDumpObject(const char *name, JSObject *obj) |
|
5785 { |
|
5786 if (obj) { |
|
5787 fprintf(stderr, " %s: ", name); |
|
5788 dumpValue(ObjectValue(*obj)); |
|
5789 fputc('\n', stderr); |
|
5790 } |
|
5791 } |
|
5792 |
|
5793 static void |
|
5794 MaybeDumpValue(const char *name, const Value &v) |
|
5795 { |
|
5796 if (!v.isNull()) { |
|
5797 fprintf(stderr, " %s: ", name); |
|
5798 dumpValue(v); |
|
5799 fputc('\n', stderr); |
|
5800 } |
|
5801 } |
|
5802 |
|
5803 JS_FRIEND_API(void) |
|
5804 js_DumpInterpreterFrame(JSContext *cx, InterpreterFrame *start) |
|
5805 { |
|
5806 /* This should only called during live debugging. */ |
|
5807 ScriptFrameIter i(cx, ScriptFrameIter::GO_THROUGH_SAVED); |
|
5808 if (!start) { |
|
5809 if (i.done()) { |
|
5810 fprintf(stderr, "no stack for cx = %p\n", (void*) cx); |
|
5811 return; |
|
5812 } |
|
5813 } else { |
|
5814 while (!i.done() && !i.isJit() && i.interpFrame() != start) |
|
5815 ++i; |
|
5816 |
|
5817 if (i.done()) { |
|
5818 fprintf(stderr, "fp = %p not found in cx = %p\n", |
|
5819 (void *)start, (void *)cx); |
|
5820 return; |
|
5821 } |
|
5822 } |
|
5823 |
|
5824 for (; !i.done(); ++i) { |
|
5825 if (i.isJit()) |
|
5826 fprintf(stderr, "JIT frame\n"); |
|
5827 else |
|
5828 fprintf(stderr, "InterpreterFrame at %p\n", (void *) i.interpFrame()); |
|
5829 |
|
5830 if (i.isFunctionFrame()) { |
|
5831 fprintf(stderr, "callee fun: "); |
|
5832 dumpValue(i.calleev()); |
|
5833 } else { |
|
5834 fprintf(stderr, "global frame, no callee"); |
|
5835 } |
|
5836 fputc('\n', stderr); |
|
5837 |
|
5838 fprintf(stderr, "file %s line %u\n", |
|
5839 i.script()->filename(), (unsigned) i.script()->lineno()); |
|
5840 |
|
5841 if (jsbytecode *pc = i.pc()) { |
|
5842 fprintf(stderr, " pc = %p\n", pc); |
|
5843 fprintf(stderr, " current op: %s\n", js_CodeName[*pc]); |
|
5844 MaybeDumpObject("staticScope", i.script()->getStaticScope(pc)); |
|
5845 } |
|
5846 MaybeDumpValue("this", i.thisv()); |
|
5847 if (!i.isJit()) { |
|
5848 fprintf(stderr, " rval: "); |
|
5849 dumpValue(i.interpFrame()->returnValue()); |
|
5850 fputc('\n', stderr); |
|
5851 } |
|
5852 |
|
5853 fprintf(stderr, " flags:"); |
|
5854 if (i.isConstructing()) |
|
5855 fprintf(stderr, " constructing"); |
|
5856 if (!i.isJit() && i.interpFrame()->isDebuggerFrame()) |
|
5857 fprintf(stderr, " debugger"); |
|
5858 if (i.isEvalFrame()) |
|
5859 fprintf(stderr, " eval"); |
|
5860 if (!i.isJit() && i.interpFrame()->isYielding()) |
|
5861 fprintf(stderr, " yielding"); |
|
5862 if (!i.isJit() && i.interpFrame()->isGeneratorFrame()) |
|
5863 fprintf(stderr, " generator"); |
|
5864 fputc('\n', stderr); |
|
5865 |
|
5866 fprintf(stderr, " scopeChain: (JSObject *) %p\n", (void *) i.scopeChain()); |
|
5867 |
|
5868 fputc('\n', stderr); |
|
5869 } |
|
5870 } |
|
5871 |
|
5872 #endif /* DEBUG */ |
|
5873 |
|
5874 JS_FRIEND_API(void) |
|
5875 js_DumpBacktrace(JSContext *cx) |
|
5876 { |
|
5877 Sprinter sprinter(cx); |
|
5878 sprinter.init(); |
|
5879 size_t depth = 0; |
|
5880 for (ScriptFrameIter i(cx); !i.done(); ++i, ++depth) { |
|
5881 const char *filename = JS_GetScriptFilename(i.script()); |
|
5882 unsigned line = JS_PCToLineNumber(cx, i.script(), i.pc()); |
|
5883 JSScript *script = i.script(); |
|
5884 sprinter.printf("#%d %14p %s:%d (%p @ %d)\n", |
|
5885 depth, (i.isJit() ? 0 : i.interpFrame()), filename, line, |
|
5886 script, script->pcToOffset(i.pc())); |
|
5887 } |
|
5888 fprintf(stdout, "%s", sprinter.string()); |
|
5889 } |
|
5890 void |
|
5891 JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes) |
|
5892 { |
|
5893 if (hasDynamicSlots()) |
|
5894 sizes->mallocHeapSlots += mallocSizeOf(slots); |
|
5895 |
|
5896 if (hasDynamicElements()) { |
|
5897 js::ObjectElements *elements = getElementsHeader(); |
|
5898 sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements); |
|
5899 } |
|
5900 |
|
5901 // Other things may be measured in the future if DMD indicates it is worthwhile. |
|
5902 if (is<JSFunction>() || |
|
5903 is<JSObject>() || |
|
5904 is<ArrayObject>() || |
|
5905 is<CallObject>() || |
|
5906 is<RegExpObject>() || |
|
5907 is<ProxyObject>()) |
|
5908 { |
|
5909 // Do nothing. But this function is hot, and we win by getting the |
|
5910 // common cases out of the way early. Some stats on the most common |
|
5911 // classes, as measured during a vanilla browser session: |
|
5912 // - (53.7%, 53.7%): Function |
|
5913 // - (18.0%, 71.7%): Object |
|
5914 // - (16.9%, 88.6%): Array |
|
5915 // - ( 3.9%, 92.5%): Call |
|
5916 // - ( 2.8%, 95.3%): RegExp |
|
5917 // - ( 1.0%, 96.4%): Proxy |
|
5918 |
|
5919 } else if (is<ArgumentsObject>()) { |
|
5920 sizes->mallocHeapArgumentsData += as<ArgumentsObject>().sizeOfMisc(mallocSizeOf); |
|
5921 } else if (is<RegExpStaticsObject>()) { |
|
5922 sizes->mallocHeapRegExpStatics += as<RegExpStaticsObject>().sizeOfData(mallocSizeOf); |
|
5923 } else if (is<PropertyIteratorObject>()) { |
|
5924 sizes->mallocHeapPropertyIteratorData += as<PropertyIteratorObject>().sizeOfMisc(mallocSizeOf); |
|
5925 } else if (is<ArrayBufferObject>() || is<SharedArrayBufferObject>()) { |
|
5926 ArrayBufferObject::addSizeOfExcludingThis(this, mallocSizeOf, sizes); |
|
5927 #ifdef JS_ION |
|
5928 } else if (is<AsmJSModuleObject>()) { |
|
5929 as<AsmJSModuleObject>().addSizeOfMisc(mallocSizeOf, &sizes->nonHeapCodeAsmJS, |
|
5930 &sizes->mallocHeapAsmJSModuleData); |
|
5931 #endif |
|
5932 #ifdef JS_HAS_CTYPES |
|
5933 } else { |
|
5934 // This must be the last case. |
|
5935 sizes->mallocHeapCtypesData += |
|
5936 js::SizeOfDataIfCDataObject(mallocSizeOf, const_cast<JSObject *>(this)); |
|
5937 #endif |
|
5938 } |
|
5939 } |