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