michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS object implementation. michael@0: */ michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/TemplateLib.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsarray.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfriendapi.h" michael@0: #include "jsfun.h" michael@0: #include "jsgc.h" michael@0: #include "jsiter.h" michael@0: #include "jsnum.h" michael@0: #include "jsopcode.h" michael@0: #include "jsprf.h" michael@0: #include "jsproxy.h" michael@0: #include "jsscript.h" michael@0: #include "jsstr.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: #include "jswatchpoint.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "builtin/Object.h" michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "gc/Marking.h" michael@0: #include "jit/AsmJSModule.h" michael@0: #include "jit/BaselineJIT.h" michael@0: #include "js/MemoryMetrics.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "vm/ArgumentsObject.h" michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/ProxyObject.h" michael@0: #include "vm/RegExpStaticsObject.h" michael@0: #include "vm/Shape.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsboolinlines.h" michael@0: #include "jscntxtinlines.h" michael@0: #include "jscompartmentinlines.h" michael@0: michael@0: #include "vm/ArrayObject-inl.h" michael@0: #include "vm/BooleanObject-inl.h" michael@0: #include "vm/NumberObject-inl.h" michael@0: #include "vm/ObjectImpl-inl.h" michael@0: #include "vm/Runtime-inl.h" michael@0: #include "vm/Shape-inl.h" michael@0: #include "vm/StringObject-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::Maybe; michael@0: using mozilla::RoundUpPow2; michael@0: michael@0: JS_STATIC_ASSERT(int32_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value)) == int64_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value))); michael@0: michael@0: const Class JSObject::class_ = { michael@0: js_Object_str, michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Object), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: const Class* const js::ObjectClassPtr = &JSObject::class_; michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: JS_ObjectToInnerObject(JSContext *cx, HandleObject obj) michael@0: { michael@0: if (!obj) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INACTIVE); michael@0: return nullptr; michael@0: } michael@0: return GetInnerObject(cx, obj); michael@0: } michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: JS_ObjectToOuterObject(JSContext *cx, HandleObject obj) michael@0: { michael@0: assertSameCompartment(cx, obj); michael@0: return GetOuterObject(cx, obj); michael@0: } michael@0: michael@0: JSObject * michael@0: js::NonNullObject(JSContext *cx, const Value &v) michael@0: { michael@0: if (v.isPrimitive()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); michael@0: return nullptr; michael@0: } michael@0: return &v.toObject(); michael@0: } michael@0: michael@0: const char * michael@0: js::InformalValueTypeName(const Value &v) michael@0: { michael@0: if (v.isObject()) michael@0: return v.toObject().getClass()->name; michael@0: if (v.isString()) michael@0: return "string"; michael@0: if (v.isNumber()) michael@0: return "number"; michael@0: if (v.isBoolean()) michael@0: return "boolean"; michael@0: if (v.isNull()) michael@0: return "null"; michael@0: if (v.isUndefined()) michael@0: return "undefined"; michael@0: return "value"; michael@0: } michael@0: michael@0: bool michael@0: js::NewPropertyDescriptorObject(JSContext *cx, Handle desc, michael@0: MutableHandleValue vp) michael@0: { michael@0: if (!desc.object()) { michael@0: vp.setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: /* We have our own property, so start creating the descriptor. */ michael@0: AutoPropDescRooter d(cx); michael@0: michael@0: d.initFromPropertyDescriptor(desc); michael@0: if (!d.makeObject(cx)) michael@0: return false; michael@0: vp.set(d.pd()); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: PropDesc::initFromPropertyDescriptor(Handle desc) michael@0: { michael@0: isUndefined_ = false; michael@0: pd_.setUndefined(); michael@0: attrs = uint8_t(desc.attributes()); michael@0: JS_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER))); michael@0: if (desc.hasGetterOrSetterObject()) { michael@0: hasGet_ = true; michael@0: get_ = desc.hasGetterObject() && desc.getterObject() michael@0: ? ObjectValue(*desc.getterObject()) michael@0: : UndefinedValue(); michael@0: hasSet_ = true; michael@0: set_ = desc.hasSetterObject() && desc.setterObject() michael@0: ? ObjectValue(*desc.setterObject()) michael@0: : UndefinedValue(); michael@0: hasValue_ = false; michael@0: value_.setUndefined(); michael@0: hasWritable_ = false; michael@0: } else { michael@0: hasGet_ = false; michael@0: get_.setUndefined(); michael@0: hasSet_ = false; michael@0: set_.setUndefined(); michael@0: hasValue_ = true; michael@0: value_ = desc.value(); michael@0: hasWritable_ = true; michael@0: } michael@0: hasEnumerable_ = true; michael@0: hasConfigurable_ = true; michael@0: } michael@0: michael@0: bool michael@0: PropDesc::makeObject(JSContext *cx) michael@0: { michael@0: MOZ_ASSERT(!isUndefined()); michael@0: michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: const JSAtomState &names = cx->names(); michael@0: RootedValue configurableVal(cx, BooleanValue((attrs & JSPROP_PERMANENT) == 0)); michael@0: RootedValue enumerableVal(cx, BooleanValue((attrs & JSPROP_ENUMERATE) != 0)); michael@0: RootedValue writableVal(cx, BooleanValue((attrs & JSPROP_READONLY) == 0)); michael@0: if ((hasConfigurable() && michael@0: !JSObject::defineProperty(cx, obj, names.configurable, configurableVal)) || michael@0: (hasEnumerable() && michael@0: !JSObject::defineProperty(cx, obj, names.enumerable, enumerableVal)) || michael@0: (hasGet() && michael@0: !JSObject::defineProperty(cx, obj, names.get, getterValue())) || michael@0: (hasSet() && michael@0: !JSObject::defineProperty(cx, obj, names.set, setterValue())) || michael@0: (hasValue() && michael@0: !JSObject::defineProperty(cx, obj, names.value, value())) || michael@0: (hasWritable() && michael@0: !JSObject::defineProperty(cx, obj, names.writable, writableVal))) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: pd_.setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: // FIXME: Call TrapGetOwnProperty directly once ScriptedIndirectProxies is removed michael@0: if (obj->is()) michael@0: return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc); michael@0: michael@0: RootedObject pobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!HasOwnProperty(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) michael@0: return false; michael@0: if (!shape) { michael@0: desc.object().set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: bool doGet = true; michael@0: if (pobj->isNative()) { michael@0: desc.setAttributes(GetShapeAttributes(pobj, shape)); michael@0: if (desc.hasGetterOrSetterObject()) { michael@0: MOZ_ASSERT(desc.isShared()); michael@0: doGet = false; michael@0: if (desc.hasGetterObject()) michael@0: desc.setGetterObject(shape->getterObject()); michael@0: if (desc.hasSetterObject()) michael@0: desc.setSetterObject(shape->setterObject()); michael@0: } else { michael@0: // This is either a straight-up data property or (rarely) a michael@0: // property with a JSPropertyOp getter/setter. The latter must be michael@0: // reported to the caller as a plain data property, so don't michael@0: // populate desc.getter/setter, and mask away the SHARED bit. michael@0: desc.attributesRef() &= ~JSPROP_SHARED; michael@0: } michael@0: } else { michael@0: if (!JSObject::getGenericAttributes(cx, pobj, id, &desc.attributesRef())) michael@0: return false; michael@0: } michael@0: michael@0: RootedValue value(cx); michael@0: if (doGet && !JSObject::getGeneric(cx, obj, obj, id, &value)) michael@0: return false; michael@0: michael@0: desc.value().set(value); michael@0: desc.object().set(obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) michael@0: { michael@0: Rooted desc(cx); michael@0: return GetOwnPropertyDescriptor(cx, obj, id, &desc) && michael@0: NewPropertyDescriptorObject(cx, desc, vp); michael@0: } michael@0: michael@0: bool michael@0: js::GetFirstArgumentAsObject(JSContext *cx, const CallArgs &args, const char *method, michael@0: MutableHandleObject objp) michael@0: { michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: method, "0", "s"); michael@0: return false; michael@0: } michael@0: michael@0: HandleValue v = args[0]; michael@0: if (!v.isObject()) { michael@0: char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); michael@0: if (!bytes) michael@0: return false; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: bytes, "not an object"); michael@0: js_free(bytes); michael@0: return false; michael@0: } michael@0: michael@0: objp.set(&v.toObject()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: HasProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, bool *foundp) michael@0: { michael@0: if (!JSObject::hasProperty(cx, obj, id, foundp)) michael@0: return false; michael@0: if (!*foundp) { michael@0: vp.setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * We must go through the method read barrier in case id is 'get' or 'set'. michael@0: * There is no obvious way to defer cloning a joined function object whose michael@0: * identity will be used by DefinePropertyOnObject, e.g., or reflected via michael@0: * js::GetOwnPropertyDescriptor, as the getter or setter callable object. michael@0: */ michael@0: return !!JSObject::getGeneric(cx, obj, obj, id, vp); michael@0: } michael@0: michael@0: bool michael@0: PropDesc::initialize(JSContext *cx, const Value &origval, bool checkAccessors) michael@0: { michael@0: RootedValue v(cx, origval); michael@0: michael@0: /* 8.10.5 step 1 */ michael@0: if (v.isPrimitive()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); michael@0: return false; michael@0: } michael@0: RootedObject desc(cx, &v.toObject()); michael@0: michael@0: /* Make a copy of the descriptor. We might need it later. */ michael@0: pd_ = v; michael@0: michael@0: isUndefined_ = false; michael@0: michael@0: /* michael@0: * Start with the proper defaults. XXX shouldn't be necessary when we get michael@0: * rid of PropDesc::attributes() michael@0: */ michael@0: attrs = JSPROP_PERMANENT | JSPROP_READONLY; michael@0: michael@0: bool found = false; michael@0: RootedId id(cx); michael@0: michael@0: /* 8.10.5 step 3 */ michael@0: id = NameToId(cx->names().enumerable); michael@0: if (!HasProperty(cx, desc, id, &v, &found)) michael@0: return false; michael@0: if (found) { michael@0: hasEnumerable_ = true; michael@0: if (ToBoolean(v)) michael@0: attrs |= JSPROP_ENUMERATE; michael@0: } michael@0: michael@0: /* 8.10.5 step 4 */ michael@0: id = NameToId(cx->names().configurable); michael@0: if (!HasProperty(cx, desc, id, &v, &found)) michael@0: return false; michael@0: if (found) { michael@0: hasConfigurable_ = true; michael@0: if (ToBoolean(v)) michael@0: attrs &= ~JSPROP_PERMANENT; michael@0: } michael@0: michael@0: /* 8.10.5 step 5 */ michael@0: id = NameToId(cx->names().value); michael@0: if (!HasProperty(cx, desc, id, &v, &found)) michael@0: return false; michael@0: if (found) { michael@0: hasValue_ = true; michael@0: value_ = v; michael@0: } michael@0: michael@0: /* 8.10.6 step 6 */ michael@0: id = NameToId(cx->names().writable); michael@0: if (!HasProperty(cx, desc, id, &v, &found)) michael@0: return false; michael@0: if (found) { michael@0: hasWritable_ = true; michael@0: if (ToBoolean(v)) michael@0: attrs &= ~JSPROP_READONLY; michael@0: } michael@0: michael@0: /* 8.10.7 step 7 */ michael@0: id = NameToId(cx->names().get); michael@0: if (!HasProperty(cx, desc, id, &v, &found)) michael@0: return false; michael@0: if (found) { michael@0: hasGet_ = true; michael@0: get_ = v; michael@0: attrs |= JSPROP_GETTER | JSPROP_SHARED; michael@0: attrs &= ~JSPROP_READONLY; michael@0: if (checkAccessors && !checkGetter(cx)) michael@0: return false; michael@0: } michael@0: michael@0: /* 8.10.7 step 8 */ michael@0: id = NameToId(cx->names().set); michael@0: if (!HasProperty(cx, desc, id, &v, &found)) michael@0: return false; michael@0: if (found) { michael@0: hasSet_ = true; michael@0: set_ = v; michael@0: attrs |= JSPROP_SETTER | JSPROP_SHARED; michael@0: attrs &= ~JSPROP_READONLY; michael@0: if (checkAccessors && !checkSetter(cx)) michael@0: return false; michael@0: } michael@0: michael@0: /* 8.10.7 step 9 */ michael@0: if ((hasGet() || hasSet()) && (hasValue() || hasWritable())) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_DESCRIPTOR); michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER))); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: PropDesc::complete() michael@0: { michael@0: if (isGenericDescriptor() || isDataDescriptor()) { michael@0: if (!hasValue_) { michael@0: hasValue_ = true; michael@0: value_.setUndefined(); michael@0: } michael@0: if (!hasWritable_) { michael@0: hasWritable_ = true; michael@0: attrs |= JSPROP_READONLY; michael@0: } michael@0: } else { michael@0: if (!hasGet_) { michael@0: hasGet_ = true; michael@0: get_.setUndefined(); michael@0: } michael@0: if (!hasSet_) { michael@0: hasSet_ = true; michael@0: set_.setUndefined(); michael@0: } michael@0: } michael@0: if (!hasEnumerable_) { michael@0: hasEnumerable_ = true; michael@0: attrs &= ~JSPROP_ENUMERATE; michael@0: } michael@0: if (!hasConfigurable_) { michael@0: hasConfigurable_ = true; michael@0: attrs |= JSPROP_PERMANENT; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: js::Throw(JSContext *cx, jsid id, unsigned errorNumber) michael@0: { michael@0: JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 1); michael@0: michael@0: JSString *idstr = IdToString(cx, id); michael@0: if (!idstr) michael@0: return false; michael@0: JSAutoByteString bytes(cx, idstr); michael@0: if (!bytes) michael@0: return false; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, errorNumber, bytes.ptr()); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: js::Throw(JSContext *cx, JSObject *obj, unsigned errorNumber) michael@0: { michael@0: if (js_ErrorFormatString[errorNumber].argCount == 1) { michael@0: RootedValue val(cx, ObjectValue(*obj)); michael@0: js_ReportValueErrorFlags(cx, JSREPORT_ERROR, errorNumber, michael@0: JSDVG_IGNORE_STACK, val, NullPtr(), michael@0: nullptr, nullptr); michael@0: } else { michael@0: JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 0); michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, errorNumber); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: Reject(JSContext *cx, unsigned errorNumber, bool throwError, jsid id, bool *rval) michael@0: { michael@0: if (throwError) michael@0: return Throw(cx, id, errorNumber); michael@0: michael@0: *rval = false; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Reject(JSContext *cx, JSObject *obj, unsigned errorNumber, bool throwError, bool *rval) michael@0: { michael@0: if (throwError) michael@0: return Throw(cx, obj, errorNumber); michael@0: michael@0: *rval = false; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Reject(JSContext *cx, HandleId id, unsigned errorNumber, bool throwError, bool *rval) michael@0: { michael@0: if (throwError) michael@0: return Throw(cx, id, errorNumber); michael@0: michael@0: *rval = false; michael@0: return true; michael@0: } michael@0: michael@0: // See comments on CheckDefineProperty in jsobj.h. michael@0: // michael@0: // DefinePropertyOnObject has its own implementation of these checks. michael@0: // michael@0: JS_FRIEND_API(bool) michael@0: js::CheckDefineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: if (!obj->isNative()) michael@0: return true; michael@0: michael@0: // ES5 8.12.9 Step 1. Even though we know obj is native, we use generic michael@0: // APIs for shorter, more readable code. michael@0: Rooted desc(cx); michael@0: if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: michael@0: // This does not have to check obj's extensibility when !desc.obj (steps michael@0: // 2-3) because the low-level methods JSObject::{add,put}Property check michael@0: // for that. michael@0: if (desc.object() && desc.isPermanent()) { michael@0: // Steps 6-11, skipping step 10.a.ii. Prohibit redefining a permanent michael@0: // property with different metadata, except to make a writable property michael@0: // non-writable. michael@0: if (getter != desc.getter() || michael@0: setter != desc.setter() || michael@0: (attrs != desc.attributes() && attrs != (desc.attributes() | JSPROP_READONLY))) michael@0: { michael@0: return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); michael@0: } michael@0: michael@0: // Step 10.a.ii. Prohibit changing the value of a non-configurable, michael@0: // non-writable data property. michael@0: if ((desc.attributes() & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_READONLY)) == JSPROP_READONLY) { michael@0: bool same; michael@0: if (!SameValue(cx, value, desc.value(), &same)) michael@0: return false; michael@0: if (!same) michael@0: return JSObject::reportReadOnly(cx, id); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DefinePropertyOnObject(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, michael@0: bool throwError, bool *rval) michael@0: { michael@0: /* 8.12.9 step 1. */ michael@0: RootedShape shape(cx); michael@0: RootedObject obj2(cx); michael@0: JS_ASSERT(!obj->getOps()->lookupGeneric); michael@0: if (!HasOwnProperty(cx, nullptr, obj, id, &obj2, &shape)) michael@0: return false; michael@0: michael@0: JS_ASSERT(!obj->getOps()->defineProperty); michael@0: michael@0: /* 8.12.9 steps 2-4. */ michael@0: if (!shape) { michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (!extensible) michael@0: return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); michael@0: michael@0: *rval = true; michael@0: michael@0: if (desc.isGenericDescriptor() || desc.isDataDescriptor()) { michael@0: JS_ASSERT(!obj->getOps()->defineProperty); michael@0: RootedValue v(cx, desc.hasValue() ? desc.value() : UndefinedValue()); michael@0: return baseops::DefineGeneric(cx, obj, id, v, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: desc.attributes()); michael@0: } michael@0: michael@0: JS_ASSERT(desc.isAccessorDescriptor()); michael@0: michael@0: return baseops::DefineGeneric(cx, obj, id, UndefinedHandleValue, michael@0: desc.getter(), desc.setter(), desc.attributes()); michael@0: } michael@0: michael@0: /* 8.12.9 steps 5-6 (note 5 is merely a special case of 6). */ michael@0: RootedValue v(cx); michael@0: michael@0: JS_ASSERT(obj == obj2); michael@0: michael@0: bool shapeDataDescriptor = true, michael@0: shapeAccessorDescriptor = false, michael@0: shapeWritable = true, michael@0: shapeConfigurable = true, michael@0: shapeEnumerable = true, michael@0: shapeHasDefaultGetter = true, michael@0: shapeHasDefaultSetter = true, michael@0: shapeHasGetterValue = false, michael@0: shapeHasSetterValue = false; michael@0: uint8_t shapeAttributes = GetShapeAttributes(obj, shape); michael@0: if (!IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: shapeDataDescriptor = shape->isDataDescriptor(); michael@0: shapeAccessorDescriptor = shape->isAccessorDescriptor(); michael@0: shapeWritable = shape->writable(); michael@0: shapeConfigurable = shape->configurable(); michael@0: shapeEnumerable = shape->enumerable(); michael@0: shapeHasDefaultGetter = shape->hasDefaultGetter(); michael@0: shapeHasDefaultSetter = shape->hasDefaultSetter(); michael@0: shapeHasGetterValue = shape->hasGetterValue(); michael@0: shapeHasSetterValue = shape->hasSetterValue(); michael@0: shapeAttributes = shape->attributes(); michael@0: } michael@0: michael@0: do { michael@0: if (desc.isAccessorDescriptor()) { michael@0: if (!shapeAccessorDescriptor) michael@0: break; michael@0: michael@0: if (desc.hasGet()) { michael@0: bool same; michael@0: if (!SameValue(cx, desc.getterValue(), shape->getterOrUndefined(), &same)) michael@0: return false; michael@0: if (!same) michael@0: break; michael@0: } michael@0: michael@0: if (desc.hasSet()) { michael@0: bool same; michael@0: if (!SameValue(cx, desc.setterValue(), shape->setterOrUndefined(), &same)) michael@0: return false; michael@0: if (!same) michael@0: break; michael@0: } michael@0: } else { michael@0: /* michael@0: * Determine the current value of the property once, if the current michael@0: * value might actually need to be used or preserved later. NB: we michael@0: * guard on whether the current property is a data descriptor to michael@0: * avoid calling a getter; we won't need the value if it's not a michael@0: * data descriptor. michael@0: */ michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: v = obj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); michael@0: } else if (shape->isDataDescriptor()) { michael@0: /* michael@0: * We must rule out a non-configurable js::PropertyOp-guarded michael@0: * property becoming a writable unguarded data property, since michael@0: * such a property can have its value changed to one the getter michael@0: * and setter preclude. michael@0: * michael@0: * A desc lacking writable but with value is a data descriptor michael@0: * and we must reject it as if it had writable: true if current michael@0: * is writable. michael@0: */ michael@0: if (!shape->configurable() && michael@0: (!shape->hasDefaultGetter() || !shape->hasDefaultSetter()) && michael@0: desc.isDataDescriptor() && michael@0: (desc.hasWritable() ? desc.writable() : shape->writable())) michael@0: { michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } michael@0: michael@0: if (!NativeGet(cx, obj, obj2, shape, &v)) michael@0: return false; michael@0: } michael@0: michael@0: if (desc.isDataDescriptor()) { michael@0: if (!shapeDataDescriptor) michael@0: break; michael@0: michael@0: bool same; michael@0: if (desc.hasValue()) { michael@0: if (!SameValue(cx, desc.value(), v, &same)) michael@0: return false; michael@0: if (!same) { michael@0: /* michael@0: * Insist that a non-configurable js::PropertyOp data michael@0: * property is frozen at exactly the last-got value. michael@0: * michael@0: * Duplicate the first part of the big conjunction that michael@0: * we tested above, rather than add a local bool flag. michael@0: * Likewise, don't try to keep shape->writable() in a michael@0: * flag we veto from true to false for non-configurable michael@0: * PropertyOp-based data properties and test before the michael@0: * SameValue check later on in order to re-use that "if michael@0: * (!SameValue) Reject" logic. michael@0: * michael@0: * This function is large and complex enough that it michael@0: * seems best to repeat a small bit of code and return michael@0: * Reject(...) ASAP, instead of being clever. michael@0: */ michael@0: if (!shapeConfigurable && michael@0: (!shape->hasDefaultGetter() || !shape->hasDefaultSetter())) michael@0: { michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: if (desc.hasWritable() && desc.writable() != shapeWritable) michael@0: break; michael@0: } else { michael@0: /* The only fields in desc will be handled below. */ michael@0: JS_ASSERT(desc.isGenericDescriptor()); michael@0: } michael@0: } michael@0: michael@0: if (desc.hasConfigurable() && desc.configurable() != shapeConfigurable) michael@0: break; michael@0: if (desc.hasEnumerable() && desc.enumerable() != shapeEnumerable) michael@0: break; michael@0: michael@0: /* The conditions imposed by step 5 or step 6 apply. */ michael@0: *rval = true; michael@0: return true; michael@0: } while (0); michael@0: michael@0: /* 8.12.9 step 7. */ michael@0: if (!shapeConfigurable) { michael@0: if ((desc.hasConfigurable() && desc.configurable()) || michael@0: (desc.hasEnumerable() && desc.enumerable() != shape->enumerable())) { michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } michael@0: } michael@0: michael@0: bool callDelProperty = false; michael@0: michael@0: if (desc.isGenericDescriptor()) { michael@0: /* 8.12.9 step 8, no validation required */ michael@0: } else if (desc.isDataDescriptor() != shapeDataDescriptor) { michael@0: /* 8.12.9 step 9. */ michael@0: if (!shapeConfigurable) michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } else if (desc.isDataDescriptor()) { michael@0: /* 8.12.9 step 10. */ michael@0: JS_ASSERT(shapeDataDescriptor); michael@0: if (!shapeConfigurable && !shape->writable()) { michael@0: if (desc.hasWritable() && desc.writable()) michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: if (desc.hasValue()) { michael@0: bool same; michael@0: if (!SameValue(cx, desc.value(), v, &same)) michael@0: return false; michael@0: if (!same) michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } michael@0: } michael@0: michael@0: callDelProperty = !shapeHasDefaultGetter || !shapeHasDefaultSetter; michael@0: } else { michael@0: /* 8.12.9 step 11. */ michael@0: JS_ASSERT(desc.isAccessorDescriptor() && shape->isAccessorDescriptor()); michael@0: if (!shape->configurable()) { michael@0: if (desc.hasSet()) { michael@0: bool same; michael@0: if (!SameValue(cx, desc.setterValue(), shape->setterOrUndefined(), &same)) michael@0: return false; michael@0: if (!same) michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } michael@0: michael@0: if (desc.hasGet()) { michael@0: bool same; michael@0: if (!SameValue(cx, desc.getterValue(), shape->getterOrUndefined(), &same)) michael@0: return false; michael@0: if (!same) michael@0: return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* 8.12.9 step 12. */ michael@0: unsigned attrs; michael@0: PropertyOp getter; michael@0: StrictPropertyOp setter; michael@0: if (desc.isGenericDescriptor()) { michael@0: unsigned changed = 0; michael@0: if (desc.hasConfigurable()) michael@0: changed |= JSPROP_PERMANENT; michael@0: if (desc.hasEnumerable()) michael@0: changed |= JSPROP_ENUMERATE; michael@0: michael@0: attrs = (shapeAttributes & ~changed) | (desc.attributes() & changed); michael@0: getter = IsImplicitDenseOrTypedArrayElement(shape) ? JS_PropertyStub : shape->getter(); michael@0: setter = IsImplicitDenseOrTypedArrayElement(shape) ? JS_StrictPropertyStub : shape->setter(); michael@0: } else if (desc.isDataDescriptor()) { michael@0: unsigned unchanged = 0; michael@0: if (!desc.hasConfigurable()) michael@0: unchanged |= JSPROP_PERMANENT; michael@0: if (!desc.hasEnumerable()) michael@0: unchanged |= JSPROP_ENUMERATE; michael@0: /* Watch out for accessor -> data transformations here. */ michael@0: if (!desc.hasWritable() && shapeDataDescriptor) michael@0: unchanged |= JSPROP_READONLY; michael@0: michael@0: if (desc.hasValue()) michael@0: v = desc.value(); michael@0: attrs = (desc.attributes() & ~unchanged) | (shapeAttributes & unchanged); michael@0: getter = JS_PropertyStub; michael@0: setter = JS_StrictPropertyStub; michael@0: } else { michael@0: JS_ASSERT(desc.isAccessorDescriptor()); michael@0: michael@0: /* 8.12.9 step 12. */ michael@0: unsigned changed = 0; michael@0: if (desc.hasConfigurable()) michael@0: changed |= JSPROP_PERMANENT; michael@0: if (desc.hasEnumerable()) michael@0: changed |= JSPROP_ENUMERATE; michael@0: if (desc.hasGet()) michael@0: changed |= JSPROP_GETTER | JSPROP_SHARED | JSPROP_READONLY; michael@0: if (desc.hasSet()) michael@0: changed |= JSPROP_SETTER | JSPROP_SHARED | JSPROP_READONLY; michael@0: michael@0: attrs = (desc.attributes() & changed) | (shapeAttributes & ~changed); michael@0: if (desc.hasGet()) { michael@0: getter = desc.getter(); michael@0: } else { michael@0: getter = (shapeHasDefaultGetter && !shapeHasGetterValue) michael@0: ? JS_PropertyStub michael@0: : shape->getter(); michael@0: } michael@0: if (desc.hasSet()) { michael@0: setter = desc.setter(); michael@0: } else { michael@0: setter = (shapeHasDefaultSetter && !shapeHasSetterValue) michael@0: ? JS_StrictPropertyStub michael@0: : shape->setter(); michael@0: } michael@0: } michael@0: michael@0: *rval = true; michael@0: michael@0: /* michael@0: * Since "data" properties implemented using native C functions may rely on michael@0: * side effects during setting, we must make them aware that they have been michael@0: * "assigned"; deleting the property before redefining it does the trick. michael@0: * See bug 539766, where we ran into problems when we redefined michael@0: * arguments.length without making the property aware that its value had michael@0: * been changed (which would have happened if we had deleted it before michael@0: * redefining it or we had invoked its setter to change its value). michael@0: */ michael@0: if (callDelProperty) { michael@0: bool succeeded; michael@0: if (!CallJSDeletePropertyOp(cx, obj2->getClass()->delProperty, obj2, id, &succeeded)) michael@0: return false; michael@0: } michael@0: michael@0: return baseops::DefineGeneric(cx, obj, id, v, getter, setter, attrs); michael@0: } michael@0: michael@0: /* ES6 20130308 draft 8.4.2.1 [[DefineOwnProperty]] */ michael@0: static bool michael@0: DefinePropertyOnArray(JSContext *cx, Handle arr, HandleId id, const PropDesc &desc, michael@0: bool throwError, bool *rval) michael@0: { michael@0: /* Step 2. */ michael@0: if (id == NameToId(cx->names().length)) { michael@0: // Canonicalize value, if necessary, before proceeding any further. It michael@0: // would be better if this were always/only done by ArraySetLength. michael@0: // But canonicalization may throw a RangeError (or other exception, if michael@0: // the value is an object with user-defined conversion semantics) michael@0: // before other attributes are checked. So as long as our internal michael@0: // defineProperty hook doesn't match the ECMA one, this duplicate michael@0: // checking can't be helped. michael@0: RootedValue v(cx); michael@0: if (desc.hasValue()) { michael@0: uint32_t newLen; michael@0: if (!CanonicalizeArrayLengthValue(cx, desc.value(), &newLen)) michael@0: return false; michael@0: v.setNumber(newLen); michael@0: } else { michael@0: v.setNumber(arr->length()); michael@0: } michael@0: michael@0: if (desc.hasConfigurable() && desc.configurable()) michael@0: return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); michael@0: if (desc.hasEnumerable() && desc.enumerable()) michael@0: return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); michael@0: michael@0: if (desc.isAccessorDescriptor()) michael@0: return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); michael@0: michael@0: unsigned attrs = arr->nativeLookup(cx, id)->attributes(); michael@0: if (!arr->lengthIsWritable()) { michael@0: if (desc.hasWritable() && desc.writable()) michael@0: return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); michael@0: } else { michael@0: if (desc.hasWritable() && !desc.writable()) michael@0: attrs = attrs | JSPROP_READONLY; michael@0: } michael@0: michael@0: return ArraySetLength(cx, arr, id, attrs, v, throwError); michael@0: } michael@0: michael@0: /* Step 3. */ michael@0: uint32_t index; michael@0: if (js_IdIsIndex(id, &index)) { michael@0: /* Step 3b. */ michael@0: uint32_t oldLen = arr->length(); michael@0: michael@0: /* Steps 3a, 3e. */ michael@0: if (index >= oldLen && !arr->lengthIsWritable()) michael@0: return Reject(cx, arr, JSMSG_CANT_APPEND_TO_ARRAY, throwError, rval); michael@0: michael@0: /* Steps 3f-j. */ michael@0: return DefinePropertyOnObject(cx, arr, id, desc, throwError, rval); michael@0: } michael@0: michael@0: /* Step 4. */ michael@0: return DefinePropertyOnObject(cx, arr, id, desc, throwError, rval); michael@0: } michael@0: michael@0: bool michael@0: js::DefineProperty(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, michael@0: bool throwError, bool *rval) michael@0: { michael@0: if (obj->is()) { michael@0: Rooted arr(cx, &obj->as()); michael@0: return DefinePropertyOnArray(cx, arr, id, desc, throwError, rval); michael@0: } michael@0: michael@0: if (obj->getOps()->lookupGeneric) { michael@0: /* michael@0: * FIXME: Once ScriptedIndirectProxies are removed, this code should call michael@0: * TrapDefineOwnProperty directly michael@0: */ michael@0: if (obj->is()) { michael@0: RootedValue pd(cx, desc.pd()); michael@0: return Proxy::defineProperty(cx, obj, id, pd); michael@0: } michael@0: return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); michael@0: } michael@0: michael@0: return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval); michael@0: } michael@0: michael@0: bool michael@0: js::DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue descriptor, michael@0: bool *bp) michael@0: { michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *desc = descs.append(); michael@0: if (!desc || !desc->initialize(cx, descriptor)) michael@0: return false; michael@0: michael@0: bool rval; michael@0: if (!DefineProperty(cx, obj, id, *desc, true, &rval)) michael@0: return false; michael@0: *bp = !!rval; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, michael@0: Handle descriptor, bool *bp) michael@0: { michael@0: AutoPropDescArrayRooter descs(cx); michael@0: PropDesc *desc = descs.append(); michael@0: if (!desc) michael@0: return false; michael@0: michael@0: desc->initFromPropertyDescriptor(descriptor); michael@0: michael@0: bool rval; michael@0: if (!DefineProperty(cx, obj, id, *desc, true, &rval)) michael@0: return false; michael@0: *bp = !!rval; michael@0: return true; michael@0: } michael@0: michael@0: michael@0: bool michael@0: js::ReadPropertyDescriptors(JSContext *cx, HandleObject props, bool checkAccessors, michael@0: AutoIdVector *ids, AutoPropDescArrayRooter *descs) michael@0: { michael@0: if (!GetPropertyNames(cx, props, JSITER_OWNONLY, ids)) michael@0: return false; michael@0: michael@0: RootedId id(cx); michael@0: for (size_t i = 0, len = ids->length(); i < len; i++) { michael@0: id = (*ids)[i]; michael@0: PropDesc* desc = descs->append(); michael@0: RootedValue v(cx); michael@0: if (!desc || michael@0: !JSObject::getGeneric(cx, props, props, id, &v) || michael@0: !desc->initialize(cx, v, checkAccessors)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) michael@0: { michael@0: AutoIdVector ids(cx); michael@0: AutoPropDescArrayRooter descs(cx); michael@0: if (!ReadPropertyDescriptors(cx, props, true, &ids, &descs)) michael@0: return false; michael@0: michael@0: if (obj->is()) { michael@0: bool dummy; michael@0: Rooted arr(cx, &obj->as()); michael@0: for (size_t i = 0, len = ids.length(); i < len; i++) { michael@0: if (!DefinePropertyOnArray(cx, arr, ids.handleAt(i), descs[i], true, &dummy)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: if (obj->getOps()->lookupGeneric) { michael@0: /* michael@0: * FIXME: Once ScriptedIndirectProxies are removed, this code should call michael@0: * TrapDefineOwnProperty directly michael@0: */ michael@0: if (obj->is()) { michael@0: for (size_t i = 0, len = ids.length(); i < len; i++) { michael@0: RootedValue pd(cx, descs[i].pd()); michael@0: if (!Proxy::defineProperty(cx, obj, ids.handleAt(i), pd)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: bool dummy; michael@0: return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, true, &dummy); michael@0: } michael@0: michael@0: bool dummy; michael@0: for (size_t i = 0, len = ids.length(); i < len; i++) { michael@0: if (!DefinePropertyOnObject(cx, obj, ids.handleAt(i), descs[i], true, &dummy)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: extern bool michael@0: js_PopulateObject(JSContext *cx, HandleObject newborn, HandleObject props) michael@0: { michael@0: return DefineProperties(cx, newborn, props); michael@0: } michael@0: michael@0: js::types::TypeObject* michael@0: JSObject::uninlinedGetType(JSContext *cx) michael@0: { michael@0: return getType(cx); michael@0: } michael@0: michael@0: void michael@0: JSObject::uninlinedSetType(js::types::TypeObject *newType) michael@0: { michael@0: setType(newType); michael@0: } michael@0: michael@0: /* static */ inline unsigned michael@0: JSObject::getSealedOrFrozenAttributes(unsigned attrs, ImmutabilityType it) michael@0: { michael@0: /* Make all attributes permanent; if freezing, make data attributes read-only. */ michael@0: if (it == FREEZE && !(attrs & (JSPROP_GETTER | JSPROP_SETTER))) michael@0: return JSPROP_PERMANENT | JSPROP_READONLY; michael@0: return JSPROP_PERMANENT; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it) michael@0: { michael@0: assertSameCompartment(cx, obj); michael@0: JS_ASSERT(it == SEAL || it == FREEZE); michael@0: michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (extensible && !JSObject::preventExtensions(cx, obj)) michael@0: return false; michael@0: michael@0: AutoIdVector props(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props)) michael@0: return false; michael@0: michael@0: /* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */ michael@0: JS_ASSERT_IF(obj->isNative(), obj->getDenseCapacity() == 0); michael@0: michael@0: if (obj->isNative() && !obj->inDictionaryMode() && !obj->is()) { michael@0: /* michael@0: * Seal/freeze non-dictionary objects by constructing a new shape michael@0: * hierarchy mirroring the original one, which can be shared if many michael@0: * objects with the same structure are sealed/frozen. If we use the michael@0: * generic path below then any non-empty object will be converted to michael@0: * dictionary mode. michael@0: */ michael@0: RootedShape last(cx, EmptyShape::getInitialShape(cx, obj->getClass(), michael@0: obj->getTaggedProto(), michael@0: obj->getParent(), michael@0: obj->getMetadata(), michael@0: obj->numFixedSlots(), michael@0: obj->lastProperty()->getObjectFlags())); michael@0: if (!last) michael@0: return false; michael@0: michael@0: /* Get an in order list of the shapes in this object. */ michael@0: AutoShapeVector shapes(cx); michael@0: for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) { michael@0: if (!shapes.append(&r.front())) michael@0: return false; michael@0: } michael@0: Reverse(shapes.begin(), shapes.end()); michael@0: michael@0: for (size_t i = 0; i < shapes.length(); i++) { michael@0: StackShape unrootedChild(shapes[i]); michael@0: RootedGeneric child(cx, &unrootedChild); michael@0: child->attrs |= getSealedOrFrozenAttributes(child->attrs, it); michael@0: michael@0: if (!JSID_IS_EMPTY(child->propid) && it == FREEZE) michael@0: MarkTypePropertyNonWritable(cx, obj, child->propid); michael@0: michael@0: last = cx->compartment()->propertyTree.getChild(cx, last, *child); michael@0: if (!last) michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT(obj->lastProperty()->slotSpan() == last->slotSpan()); michael@0: JS_ALWAYS_TRUE(setLastProperty(cx, obj, last)); michael@0: } else { michael@0: RootedId id(cx); michael@0: for (size_t i = 0; i < props.length(); i++) { michael@0: id = props[i]; michael@0: michael@0: unsigned attrs; michael@0: if (!getGenericAttributes(cx, obj, id, &attrs)) michael@0: return false; michael@0: michael@0: unsigned new_attrs = getSealedOrFrozenAttributes(attrs, it); michael@0: michael@0: /* If we already have the attributes we need, skip the setAttributes call. */ michael@0: if ((attrs | new_attrs) == attrs) michael@0: continue; michael@0: michael@0: attrs |= new_attrs; michael@0: if (!setGenericAttributes(cx, obj, id, &attrs)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Ordinarily ArraySetLength handles this, but we're going behind its back michael@0: // right now, so we must do this manually. Neither the custom property michael@0: // tree mutations nor the setGenericAttributes call in the above code will michael@0: // do this for us. michael@0: // michael@0: // ArraySetLength also implements the capacity <= length invariant for michael@0: // arrays with non-writable length. We don't need to do anything special michael@0: // for that, because capacity was zeroed out by preventExtensions. (See michael@0: // the assertion before the if-else above.) michael@0: if (it == FREEZE && obj->is()) michael@0: obj->getElementsHeader()->setNonwritableArrayLength(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it, bool *resultp) michael@0: { michael@0: bool extensible; michael@0: if (!JSObject::isExtensible(cx, obj, &extensible)) michael@0: return false; michael@0: if (extensible) { michael@0: *resultp = false; michael@0: return true; michael@0: } michael@0: michael@0: if (obj->is()) { michael@0: if (it == SEAL) { michael@0: // Typed arrays are always sealed. michael@0: *resultp = true; michael@0: } else { michael@0: // Typed arrays cannot be frozen, but an empty typed array is michael@0: // trivially frozen. michael@0: *resultp = (obj->as().length() == 0); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: AutoIdVector props(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props)) michael@0: return false; michael@0: michael@0: RootedId id(cx); michael@0: for (size_t i = 0, len = props.length(); i < len; i++) { michael@0: id = props[i]; michael@0: michael@0: unsigned attrs; michael@0: if (!getGenericAttributes(cx, obj, id, &attrs)) michael@0: return false; michael@0: michael@0: /* michael@0: * If the property is configurable, this object is neither sealed nor michael@0: * frozen. If the property is a writable data property, this object is michael@0: * not frozen. michael@0: */ michael@0: if (!(attrs & JSPROP_PERMANENT) || michael@0: (it == FREEZE && !(attrs & (JSPROP_READONLY | JSPROP_GETTER | JSPROP_SETTER)))) michael@0: { michael@0: *resultp = false; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: /* All properties checked out. This object is sealed/frozen. */ michael@0: *resultp = true; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ michael@0: const char * michael@0: JSObject::className(JSContext *cx, HandleObject obj) michael@0: { michael@0: assertSameCompartment(cx, obj); michael@0: michael@0: if (obj->is()) michael@0: return Proxy::className(cx, obj); michael@0: michael@0: return obj->getClass()->name; michael@0: } michael@0: michael@0: /* michael@0: * Get the GC kind to use for scripted 'new' on the given class. michael@0: * FIXME bug 547327: estimate the size from the allocation site. michael@0: */ michael@0: static inline gc::AllocKind michael@0: NewObjectGCKind(const js::Class *clasp) michael@0: { michael@0: if (clasp == &ArrayObject::class_) michael@0: return gc::FINALIZE_OBJECT8; michael@0: if (clasp == &JSFunction::class_) michael@0: return gc::FINALIZE_OBJECT2; michael@0: return gc::FINALIZE_OBJECT4; michael@0: } michael@0: michael@0: static inline JSObject * michael@0: NewObject(ExclusiveContext *cx, types::TypeObject *type_, JSObject *parent, gc::AllocKind kind, michael@0: NewObjectKind newKind) michael@0: { michael@0: const Class *clasp = type_->clasp(); michael@0: michael@0: JS_ASSERT(clasp != &ArrayObject::class_); michael@0: JS_ASSERT_IF(clasp == &JSFunction::class_, michael@0: kind == JSFunction::FinalizeKind || kind == JSFunction::ExtendedFinalizeKind); michael@0: JS_ASSERT_IF(parent, &parent->global() == cx->global()); michael@0: michael@0: RootedTypeObject type(cx, type_); michael@0: michael@0: JSObject *metadata = nullptr; michael@0: if (!NewObjectMetadata(cx, &metadata)) michael@0: return nullptr; michael@0: michael@0: // For objects which can have fixed data following the object, only use michael@0: // enough fixed slots to cover the number of reserved slots in the object, michael@0: // regardless of the allocation kind specified. michael@0: size_t nfixed = ClassCanHaveFixedData(clasp) michael@0: ? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp) michael@0: : GetGCKindSlots(kind, clasp); michael@0: michael@0: RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, type->proto(), michael@0: parent, metadata, nfixed)); michael@0: if (!shape) michael@0: return nullptr; michael@0: michael@0: gc::InitialHeap heap = GetInitialHeap(newKind, clasp); michael@0: JSObject *obj = JSObject::create(cx, kind, heap, shape, type); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: if (newKind == SingletonObject) { michael@0: RootedObject nobj(cx, obj); michael@0: if (!JSObject::setSingletonType(cx, nobj)) michael@0: return nullptr; michael@0: obj = nobj; michael@0: } michael@0: michael@0: /* michael@0: * This will cancel an already-running incremental GC from doing any more michael@0: * slices, and it will prevent any future incremental GCs. michael@0: */ michael@0: bool globalWithoutCustomTrace = clasp->trace == JS_GlobalObjectTraceHook && michael@0: !cx->compartment()->options().getTrace(); michael@0: if (clasp->trace && michael@0: !globalWithoutCustomTrace && michael@0: !(clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS)) michael@0: { michael@0: if (!cx->shouldBeJSContext()) michael@0: return nullptr; michael@0: JSRuntime *rt = cx->asJSContext()->runtime(); michael@0: rt->gcIncrementalEnabled = false; michael@0: michael@0: #ifdef DEBUG michael@0: if (rt->gcMode() == JSGC_MODE_INCREMENTAL) { michael@0: fprintf(stderr, michael@0: "The class %s has a trace hook but does not declare the\n" michael@0: "JSCLASS_IMPLEMENTS_BARRIERS flag. Please ensure that it correctly\n" michael@0: "implements write barriers and then set the flag.\n", michael@0: clasp->name); michael@0: MOZ_CRASH(); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: probes::CreateObject(cx, obj); michael@0: return obj; michael@0: } michael@0: michael@0: void michael@0: NewObjectCache::fillProto(EntryIndex entry, const Class *clasp, js::TaggedProto proto, michael@0: gc::AllocKind kind, JSObject *obj) michael@0: { michael@0: JS_ASSERT_IF(proto.isObject(), !proto.toObject()->is()); michael@0: JS_ASSERT(obj->getTaggedProto() == proto); michael@0: return fill(entry, clasp, proto.raw(), kind, obj); michael@0: } michael@0: michael@0: JSObject * michael@0: js::NewObjectWithGivenProto(ExclusiveContext *cxArg, const js::Class *clasp, michael@0: js::TaggedProto protoArg, JSObject *parentArg, michael@0: gc::AllocKind allocKind, NewObjectKind newKind) michael@0: { michael@0: if (CanBeFinalizedInBackground(allocKind, clasp)) michael@0: allocKind = GetBackgroundAllocKind(allocKind); michael@0: michael@0: NewObjectCache::EntryIndex entry = -1; michael@0: if (JSContext *cx = cxArg->maybeJSContext()) { michael@0: NewObjectCache &cache = cx->runtime()->newObjectCache; michael@0: if (protoArg.isObject() && michael@0: newKind == GenericObject && michael@0: !cx->compartment()->hasObjectMetadataCallback() && michael@0: (!parentArg || parentArg == protoArg.toObject()->getParent()) && michael@0: !protoArg.toObject()->is()) michael@0: { michael@0: if (cache.lookupProto(clasp, protoArg.toObject(), allocKind, &entry)) { michael@0: JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp)); michael@0: if (obj) { michael@0: return obj; michael@0: } else { michael@0: Rooted proto(cxArg, protoArg); michael@0: RootedObject parent(cxArg, parentArg); michael@0: obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp)); michael@0: JS_ASSERT(!obj); michael@0: parentArg = parent; michael@0: protoArg = proto; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: Rooted proto(cxArg, protoArg); michael@0: RootedObject parent(cxArg, parentArg); michael@0: michael@0: types::TypeObject *type = cxArg->getNewType(clasp, proto, nullptr); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Default parent to the parent of the prototype, which was set from michael@0: * the parent of the prototype's constructor. michael@0: */ michael@0: if (!parent && proto.isObject()) michael@0: parent = proto.toObject()->getParent(); michael@0: michael@0: RootedObject obj(cxArg, NewObject(cxArg, type, parent, allocKind, newKind)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: if (entry != -1 && !obj->hasDynamicSlots()) { michael@0: cxArg->asJSContext()->runtime()->newObjectCache.fillProto(entry, clasp, michael@0: proto, allocKind, obj); michael@0: } michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: JSObject * michael@0: js::NewObjectWithClassProtoCommon(ExclusiveContext *cxArg, michael@0: const js::Class *clasp, JSObject *protoArg, JSObject *parentArg, michael@0: gc::AllocKind allocKind, NewObjectKind newKind) michael@0: { michael@0: if (protoArg) michael@0: return NewObjectWithGivenProto(cxArg, clasp, protoArg, parentArg, allocKind, newKind); michael@0: michael@0: if (CanBeFinalizedInBackground(allocKind, clasp)) michael@0: allocKind = GetBackgroundAllocKind(allocKind); michael@0: michael@0: if (!parentArg) michael@0: parentArg = cxArg->global(); michael@0: michael@0: /* michael@0: * Use the object cache, except for classes without a cached proto key. michael@0: * On these objects, FindProto will do a dynamic property lookup to get michael@0: * global[className].prototype, where changes to either the className or michael@0: * prototype property would render the cached lookup incorrect. For classes michael@0: * with a proto key, the prototype created during class initialization is michael@0: * stored in an immutable slot on the global (except for ClearScope, which michael@0: * will flush the new object cache). michael@0: */ michael@0: JSProtoKey protoKey = GetClassProtoKey(clasp); michael@0: michael@0: NewObjectCache::EntryIndex entry = -1; michael@0: if (JSContext *cx = cxArg->maybeJSContext()) { michael@0: NewObjectCache &cache = cx->runtime()->newObjectCache; michael@0: if (parentArg->is() && michael@0: protoKey != JSProto_Null && michael@0: newKind == GenericObject && michael@0: !cx->compartment()->hasObjectMetadataCallback()) michael@0: { michael@0: if (cache.lookupGlobal(clasp, &parentArg->as(), allocKind, &entry)) { michael@0: JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp)); michael@0: if (obj) { michael@0: return obj; michael@0: } else { michael@0: RootedObject parent(cxArg, parentArg); michael@0: RootedObject proto(cxArg, protoArg); michael@0: obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp)); michael@0: JS_ASSERT(!obj); michael@0: protoArg = proto; michael@0: parentArg = parent; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: RootedObject parent(cxArg, parentArg); michael@0: RootedObject proto(cxArg, protoArg); michael@0: michael@0: if (!FindProto(cxArg, clasp, &proto)) michael@0: return nullptr; michael@0: michael@0: types::TypeObject *type = cxArg->getNewType(clasp, proto.get()); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: JSObject *obj = NewObject(cxArg, type, parent, allocKind, newKind); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: if (entry != -1 && !obj->hasDynamicSlots()) { michael@0: cxArg->asJSContext()->runtime()->newObjectCache.fillGlobal(entry, clasp, michael@0: &parent->as(), michael@0: allocKind, obj); michael@0: } michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: /* michael@0: * Create a plain object with the specified type. This bypasses getNewType to michael@0: * avoid losing creation site information for objects made by scripted 'new'. michael@0: */ michael@0: JSObject * michael@0: js::NewObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, gc::AllocKind allocKind, michael@0: NewObjectKind newKind) michael@0: { michael@0: JS_ASSERT(parent); michael@0: michael@0: JS_ASSERT(allocKind <= gc::FINALIZE_OBJECT_LAST); michael@0: if (CanBeFinalizedInBackground(allocKind, type->clasp())) michael@0: allocKind = GetBackgroundAllocKind(allocKind); michael@0: michael@0: NewObjectCache &cache = cx->runtime()->newObjectCache; michael@0: michael@0: NewObjectCache::EntryIndex entry = -1; michael@0: if (parent == type->proto().toObject()->getParent() && michael@0: newKind == GenericObject && michael@0: !cx->compartment()->hasObjectMetadataCallback()) michael@0: { michael@0: if (cache.lookupType(type, allocKind, &entry)) { michael@0: JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, type->clasp())); michael@0: if (obj) { michael@0: return obj; michael@0: } else { michael@0: obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, type->clasp())); michael@0: parent = type->proto().toObject()->getParent(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: JSObject *obj = NewObject(cx, type, parent, allocKind, newKind); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: if (entry != -1 && !obj->hasDynamicSlots()) michael@0: cache.fillType(entry, type, allocKind, obj); michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: bool michael@0: js::NewObjectScriptedCall(JSContext *cx, MutableHandleObject pobj) michael@0: { michael@0: jsbytecode *pc; michael@0: RootedScript script(cx, cx->currentScript(&pc)); michael@0: gc::AllocKind allocKind = NewObjectGCKind(&JSObject::class_); michael@0: NewObjectKind newKind = script michael@0: ? UseNewTypeForInitializer(script, pc, &JSObject::class_) michael@0: : GenericObject; michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind, newKind)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (script) { michael@0: /* Try to specialize the type of the object to the scripted call site. */ michael@0: if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) michael@0: return false; michael@0: } michael@0: michael@0: pobj.set(obj); michael@0: return true; michael@0: } michael@0: michael@0: JSObject* michael@0: js::CreateThis(JSContext *cx, const Class *newclasp, HandleObject callee) michael@0: { michael@0: RootedValue protov(cx); michael@0: if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov)) michael@0: return nullptr; michael@0: michael@0: JSObject *proto = protov.isObjectOrNull() ? protov.toObjectOrNull() : nullptr; michael@0: JSObject *parent = callee->getParent(); michael@0: gc::AllocKind kind = NewObjectGCKind(newclasp); michael@0: return NewObjectWithClassProto(cx, newclasp, proto, parent, kind); michael@0: } michael@0: michael@0: static inline JSObject * michael@0: CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, michael@0: NewObjectKind newKind) michael@0: { michael@0: if (type->hasNewScript()) { michael@0: /* michael@0: * Make an object with the type's associated finalize kind and shape, michael@0: * which reflects any properties that will definitely be added to the michael@0: * object before it is read from. michael@0: */ michael@0: RootedObject templateObject(cx, type->newScript()->templateObject); michael@0: JS_ASSERT(templateObject->type() == type); michael@0: michael@0: RootedObject res(cx, CopyInitializerObject(cx, templateObject, newKind)); michael@0: if (!res) michael@0: return nullptr; michael@0: if (newKind == SingletonObject) { michael@0: Rooted proto(cx, templateObject->getProto()); michael@0: if (!res->splicePrototype(cx, &JSObject::class_, proto)) michael@0: return nullptr; michael@0: } else { michael@0: res->setType(type); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: gc::AllocKind allocKind = NewObjectGCKind(&JSObject::class_); michael@0: return NewObjectWithType(cx, type, parent, allocKind, newKind); michael@0: } michael@0: michael@0: JSObject * michael@0: js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject *proto, michael@0: NewObjectKind newKind /* = GenericObject */) michael@0: { michael@0: RootedObject res(cx); michael@0: michael@0: if (proto) { michael@0: RootedTypeObject type(cx, cx->getNewType(&JSObject::class_, proto, &callee->as())); michael@0: if (!type) michael@0: return nullptr; michael@0: res = CreateThisForFunctionWithType(cx, type, callee->getParent(), newKind); michael@0: } else { michael@0: gc::AllocKind allocKind = NewObjectGCKind(&JSObject::class_); michael@0: res = NewObjectWithClassProto(cx, &JSObject::class_, proto, callee->getParent(), allocKind, newKind); michael@0: } michael@0: michael@0: if (res) { michael@0: JSScript *script = callee->as().getOrCreateScript(cx); michael@0: if (!script) michael@0: return nullptr; michael@0: TypeScript::SetThis(cx, script, types::Type::ObjectType(res)); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: JSObject * michael@0: js::CreateThisForFunction(JSContext *cx, HandleObject callee, NewObjectKind newKind) michael@0: { michael@0: RootedValue protov(cx); michael@0: if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov)) michael@0: return nullptr; michael@0: JSObject *proto; michael@0: if (protov.isObject()) michael@0: proto = &protov.toObject(); michael@0: else michael@0: proto = nullptr; michael@0: JSObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); michael@0: michael@0: if (obj && newKind == SingletonObject) { michael@0: RootedObject nobj(cx, obj); michael@0: michael@0: /* Reshape the singleton before passing it as the 'this' value. */ michael@0: JSObject::clear(cx, nobj); michael@0: michael@0: JSScript *calleeScript = callee->as().nonLazyScript(); michael@0: TypeScript::SetThis(cx, calleeScript, types::Type::ObjectType(nobj)); michael@0: michael@0: return nobj; michael@0: } michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: /* michael@0: * Given pc pointing after a property accessing bytecode, return true if the michael@0: * access is "object-detecting" in the sense used by web scripts, e.g., when michael@0: * checking whether document.all is defined. michael@0: */ michael@0: static bool michael@0: Detecting(JSContext *cx, JSScript *script, jsbytecode *pc) michael@0: { michael@0: JS_ASSERT(script->containsPC(pc)); michael@0: michael@0: /* General case: a branch or equality op follows the access. */ michael@0: JSOp op = JSOp(*pc); michael@0: if (js_CodeSpec[op].format & JOF_DETECTING) michael@0: return true; michael@0: michael@0: jsbytecode *endpc = script->codeEnd(); michael@0: michael@0: if (op == JSOP_NULL) { michael@0: /* michael@0: * Special case #1: handle (document.all == null). Don't sweat michael@0: * about JS1.2's revision of the equality operators here. michael@0: */ michael@0: if (++pc < endpc) { michael@0: op = JSOp(*pc); michael@0: return op == JSOP_EQ || op == JSOP_NE; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: if (op == JSOP_GETGNAME || op == JSOP_NAME) { michael@0: /* michael@0: * Special case #2: handle (document.all == undefined). Don't worry michael@0: * about a local variable named |undefined| shadowing the immutable michael@0: * global binding...because, really? michael@0: */ michael@0: JSAtom *atom = script->getAtom(GET_UINT32_INDEX(pc)); michael@0: if (atom == cx->names().undefined && michael@0: (pc += js_CodeSpec[op].length) < endpc) { michael@0: op = JSOp(*pc); michael@0: return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, michael@0: HandleId id, MutableHandleValue vp, bool strict) michael@0: { michael@0: if (MOZ_UNLIKELY(obj->watched())) { michael@0: WatchpointMap *wpmap = cx->compartment()->watchpointMap; michael@0: if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) michael@0: return false; michael@0: } michael@0: return obj->getOps()->setGeneric(cx, obj, id, vp, strict); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::nonNativeSetElement(JSContext *cx, HandleObject obj, michael@0: uint32_t index, MutableHandleValue vp, bool strict) michael@0: { michael@0: if (MOZ_UNLIKELY(obj->watched())) { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: michael@0: WatchpointMap *wpmap = cx->compartment()->watchpointMap; michael@0: if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) michael@0: return false; michael@0: } michael@0: return obj->getOps()->setElement(cx, obj, index, vp, strict); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::deleteByValue(JSContext *cx, HandleObject obj, const Value &property, bool *succeeded) michael@0: { michael@0: uint32_t index; michael@0: if (IsDefinitelyIndex(property, &index)) michael@0: return deleteElement(cx, obj, index, succeeded); michael@0: michael@0: RootedValue propval(cx, property); michael@0: michael@0: JSAtom *name = ToAtom(cx, propval); michael@0: if (!name) michael@0: return false; michael@0: michael@0: if (name->isIndex(&index)) michael@0: return deleteElement(cx, obj, index, succeeded); michael@0: michael@0: Rooted propname(cx, name->asPropertyName()); michael@0: return deleteProperty(cx, obj, propname, succeeded); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: JS_CopyPropertyFrom(JSContext *cx, HandleId id, HandleObject target, michael@0: HandleObject obj) michael@0: { michael@0: // |obj| and |cx| are generally not same-compartment with |target| here. michael@0: assertSameCompartment(cx, obj, id); michael@0: Rooted desc(cx); michael@0: michael@0: if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) michael@0: return false; michael@0: MOZ_ASSERT(desc.object()); michael@0: michael@0: // Silently skip JSPropertyOp-implemented accessors. michael@0: if (desc.getter() && !desc.hasGetterObject()) michael@0: return true; michael@0: if (desc.setter() && !desc.hasSetterObject()) michael@0: return true; michael@0: michael@0: JSAutoCompartment ac(cx, target); michael@0: RootedId wrappedId(cx, id); michael@0: if (!cx->compartment()->wrap(cx, &desc)) michael@0: return false; michael@0: if (!cx->compartment()->wrapId(cx, wrappedId.address())) michael@0: return false; michael@0: michael@0: bool ignored; michael@0: return DefineOwnProperty(cx, target, wrappedId, desc, &ignored); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: JS_CopyPropertiesFrom(JSContext *cx, HandleObject target, HandleObject obj) michael@0: { michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: AutoIdVector props(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &props)) michael@0: return false; michael@0: michael@0: for (size_t i = 0; i < props.length(); ++i) { michael@0: if (!JS_CopyPropertyFrom(cx, props.handleAt(i), target, obj)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: CopySlots(JSContext *cx, HandleObject from, HandleObject to) michael@0: { michael@0: JS_ASSERT(!from->isNative() && !to->isNative()); michael@0: JS_ASSERT(from->getClass() == to->getClass()); michael@0: michael@0: size_t n = 0; michael@0: if (from->is() && michael@0: (Wrapper::wrapperHandler(from)->flags() & michael@0: Wrapper::CROSS_COMPARTMENT)) { michael@0: to->setSlot(0, from->getSlot(0)); michael@0: to->setSlot(1, from->getSlot(1)); michael@0: n = 2; michael@0: } michael@0: michael@0: size_t span = JSCLASS_RESERVED_SLOTS(from->getClass()); michael@0: RootedValue v(cx); michael@0: for (; n < span; ++n) { michael@0: v = from->getSlot(n); michael@0: if (!cx->compartment()->wrap(cx, &v)) michael@0: return false; michael@0: to->setSlot(n, v); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js::CloneObject(JSContext *cx, HandleObject obj, Handle proto, HandleObject parent) michael@0: { michael@0: if (!obj->isNative() && !obj->is()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_CANT_CLONE_OBJECT); michael@0: return nullptr; michael@0: } michael@0: michael@0: RootedObject clone(cx, NewObjectWithGivenProto(cx, obj->getClass(), proto, parent)); michael@0: if (!clone) michael@0: return nullptr; michael@0: if (obj->isNative()) { michael@0: if (clone->is() && (obj->compartment() != clone->compartment())) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_CANT_CLONE_OBJECT); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (obj->hasPrivate()) michael@0: clone->setPrivate(obj->getPrivate()); michael@0: } else { michael@0: JS_ASSERT(obj->is()); michael@0: if (!CopySlots(cx, obj, clone)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return clone; michael@0: } michael@0: michael@0: JSObject * michael@0: js::DeepCloneObjectLiteral(JSContext *cx, HandleObject obj, NewObjectKind newKind) michael@0: { michael@0: /* NB: Keep this in sync with XDRObjectLiteral. */ michael@0: JS_ASSERT(JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()); michael@0: JS_ASSERT(obj->is() || obj->is()); michael@0: michael@0: // Result of the clone function. michael@0: RootedObject clone(cx); michael@0: michael@0: // Temporary element/slot which would be stored in the cloned object. michael@0: RootedValue v(cx); michael@0: RootedObject deepObj(cx); michael@0: michael@0: if (obj->getClass() == &ArrayObject::class_) { michael@0: clone = NewDenseUnallocatedArray(cx, obj->as().length(), nullptr, newKind); michael@0: } else { michael@0: // Object literals are tenured by default as holded by the JSScript. michael@0: JS_ASSERT(obj->isTenured()); michael@0: AllocKind kind = obj->tenuredGetAllocKind(); michael@0: Rooted typeObj(cx, obj->getType(cx)); michael@0: if (!typeObj) michael@0: return nullptr; michael@0: RootedObject parent(cx, obj->getParent()); michael@0: clone = NewObjectWithGivenProto(cx, &JSObject::class_, typeObj->proto().toObject(), michael@0: parent, kind, newKind); michael@0: } michael@0: michael@0: // Allocate the same number of slots. michael@0: if (!clone || !clone->ensureElements(cx, obj->getDenseCapacity())) michael@0: return nullptr; michael@0: michael@0: // Copy the number of initialized elements. michael@0: uint32_t initialized = obj->getDenseInitializedLength(); michael@0: if (initialized) michael@0: clone->setDenseInitializedLength(initialized); michael@0: michael@0: // Recursive copy of dense element. michael@0: for (uint32_t i = 0; i < initialized; ++i) { michael@0: v = obj->getDenseElement(i); michael@0: if (v.isObject()) { michael@0: deepObj = &v.toObject(); michael@0: deepObj = js::DeepCloneObjectLiteral(cx, deepObj, newKind); michael@0: if (!deepObj) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: v.setObject(*deepObj); michael@0: } michael@0: clone->initDenseElement(i, v); michael@0: } michael@0: michael@0: JS_ASSERT(obj->compartment() == clone->compartment()); michael@0: JS_ASSERT(!obj->hasPrivate()); michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: size_t span = shape->slotSpan(); michael@0: clone->setLastProperty(cx, clone, shape); michael@0: for (size_t i = 0; i < span; i++) { michael@0: v = obj->getSlot(i); michael@0: if (v.isObject()) { michael@0: deepObj = &v.toObject(); michael@0: deepObj = js::DeepCloneObjectLiteral(cx, deepObj, newKind); michael@0: if (!deepObj) michael@0: return nullptr; michael@0: v.setObject(*deepObj); michael@0: } michael@0: clone->setSlot(i, v); michael@0: } michael@0: michael@0: if (obj->getClass() == &ArrayObject::class_) michael@0: FixArrayType(cx, clone); michael@0: else michael@0: FixObjectType(cx, clone); michael@0: michael@0: #ifdef DEBUG michael@0: Rooted typeObj(cx, obj->getType(cx)); michael@0: Rooted cloneTypeObj(cx, clone->getType(cx)); michael@0: if (!typeObj || !cloneTypeObj) michael@0: return nullptr; michael@0: JS_ASSERT(typeObj == cloneTypeObj); michael@0: #endif michael@0: michael@0: return clone; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::XDRObjectLiteral(XDRState *xdr, MutableHandleObject obj) michael@0: { michael@0: /* NB: Keep this in sync with DeepCloneObjectLiteral. */ michael@0: michael@0: JSContext *cx = xdr->cx(); michael@0: JS_ASSERT_IF(mode == XDR_ENCODE, JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()); michael@0: michael@0: // Distinguish between objects and array classes. michael@0: uint32_t isArray = 0; michael@0: { michael@0: if (mode == XDR_ENCODE) { michael@0: JS_ASSERT(obj->is() || obj->is()); michael@0: isArray = obj->getClass() == &ArrayObject::class_ ? 1 : 0; michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&isArray)) michael@0: return false; michael@0: } michael@0: michael@0: if (isArray) { michael@0: uint32_t length; michael@0: michael@0: if (mode == XDR_ENCODE) michael@0: length = obj->as().length(); michael@0: michael@0: if (!xdr->codeUint32(&length)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: obj.set(NewDenseUnallocatedArray(cx, length, NULL, js::MaybeSingletonObject)); michael@0: michael@0: } else { michael@0: // Code the alloc kind of the object. michael@0: AllocKind kind; michael@0: { michael@0: if (mode == XDR_ENCODE) { michael@0: JS_ASSERT(obj->getClass() == &JSObject::class_); michael@0: JS_ASSERT(obj->isTenured()); michael@0: kind = obj->tenuredGetAllocKind(); michael@0: } michael@0: michael@0: if (!xdr->codeEnum32(&kind)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: obj.set(NewBuiltinClassInstance(cx, &JSObject::class_, kind, js::MaybeSingletonObject)); michael@0: } michael@0: } michael@0: michael@0: { michael@0: uint32_t capacity; michael@0: if (mode == XDR_ENCODE) michael@0: capacity = obj->getDenseCapacity(); michael@0: if (!xdr->codeUint32(&capacity)) michael@0: return false; michael@0: if (mode == XDR_DECODE) { michael@0: if (!obj->ensureElements(cx, capacity)) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint32_t initialized; michael@0: { michael@0: if (mode == XDR_ENCODE) michael@0: initialized = obj->getDenseInitializedLength(); michael@0: if (!xdr->codeUint32(&initialized)) michael@0: return false; michael@0: if (mode == XDR_DECODE) { michael@0: if (initialized) michael@0: obj->setDenseInitializedLength(initialized); michael@0: } michael@0: } michael@0: michael@0: RootedValue tmpValue(cx); michael@0: michael@0: // Recursively copy dense elements. michael@0: { michael@0: for (unsigned i = 0; i < initialized; i++) { michael@0: if (mode == XDR_ENCODE) michael@0: tmpValue = obj->getDenseElement(i); michael@0: michael@0: if (!xdr->codeConstValue(&tmpValue)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: obj->initDenseElement(i, tmpValue); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(!obj->hasPrivate()); michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: michael@0: // Code the number of slots in the vector. michael@0: unsigned nslot = 0; michael@0: michael@0: // Code ids of the object in order. As opposed to DeepCloneObjectLiteral we michael@0: // cannot just re-use the shape of the original bytecode value and we have michael@0: // to write down the shape as well as the corresponding values. Ideally we michael@0: // would have a mechanism to serialize the shape too. michael@0: js::AutoIdVector ids(cx); michael@0: { michael@0: if (mode == XDR_ENCODE && !shape->isEmptyShape()) { michael@0: nslot = shape->slotSpan(); michael@0: if (!ids.reserve(nslot)) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < nslot; i++) michael@0: ids.infallibleAppend(JSID_VOID); michael@0: michael@0: for (Shape::Range it(shape); !it.empty(); it.popFront()) { michael@0: // If we have reached the native property of the array class, we michael@0: // exit as the remaining would only be reserved slots. michael@0: if (!it.front().hasSlot()) { michael@0: JS_ASSERT(isArray); michael@0: break; michael@0: } michael@0: michael@0: JS_ASSERT(it.front().hasDefaultGetter()); michael@0: ids[it.front().slot()] = it.front().propid(); michael@0: } michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&nslot)) michael@0: return false; michael@0: michael@0: RootedAtom atom(cx); michael@0: RootedId id(cx); michael@0: uint32_t idType = 0; michael@0: for (unsigned i = 0; i < nslot; i++) { michael@0: if (mode == XDR_ENCODE) { michael@0: id = ids[i]; michael@0: if (JSID_IS_INT(id)) michael@0: idType = JSID_TYPE_INT; michael@0: else if (JSID_IS_ATOM(id)) michael@0: idType = JSID_TYPE_STRING; michael@0: else michael@0: MOZ_ASSUME_UNREACHABLE("Object property is not yet supported by XDR."); michael@0: michael@0: tmpValue = obj->getSlot(i); michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&idType)) michael@0: return false; michael@0: michael@0: if (idType == JSID_TYPE_STRING) { michael@0: if (mode == XDR_ENCODE) michael@0: atom = JSID_TO_ATOM(id); michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: if (mode == XDR_DECODE) michael@0: id = AtomToId(atom); michael@0: } else { michael@0: JS_ASSERT(idType == JSID_TYPE_INT); michael@0: uint32_t indexVal; michael@0: if (mode == XDR_ENCODE) michael@0: indexVal = uint32_t(JSID_TO_INT(id)); michael@0: if (!xdr->codeUint32(&indexVal)) michael@0: return false; michael@0: if (mode == XDR_DECODE) michael@0: id = INT_TO_JSID(int32_t(indexVal)); michael@0: } michael@0: michael@0: if (!xdr->codeConstValue(&tmpValue)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: if (!DefineNativeProperty(cx, obj, id, tmpValue, NULL, NULL, JSPROP_ENUMERATE)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT_IF(mode == XDR_DECODE, !obj->inDictionaryMode()); michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: if (isArray) michael@0: FixArrayType(cx, obj); michael@0: else michael@0: FixObjectType(cx, obj); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRObjectLiteral(XDRState *xdr, MutableHandleObject obj); michael@0: michael@0: template bool michael@0: js::XDRObjectLiteral(XDRState *xdr, MutableHandleObject obj); michael@0: michael@0: JSObject * michael@0: js::CloneObjectLiteral(JSContext *cx, HandleObject parent, HandleObject srcObj) michael@0: { michael@0: Rooted typeObj(cx); michael@0: typeObj = cx->getNewType(&JSObject::class_, cx->global()->getOrCreateObjectPrototype(cx)); michael@0: michael@0: JS_ASSERT(srcObj->getClass() == &JSObject::class_); michael@0: AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots())); michael@0: JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind()); michael@0: michael@0: RootedShape shape(cx, srcObj->lastProperty()); michael@0: return NewReshapedObject(cx, typeObj, parent, kind, shape); michael@0: } michael@0: michael@0: struct JSObject::TradeGutsReserved { michael@0: Vector avals; michael@0: Vector bvals; michael@0: int newafixed; michael@0: int newbfixed; michael@0: RootedShape newashape; michael@0: RootedShape newbshape; michael@0: HeapSlot *newaslots; michael@0: HeapSlot *newbslots; michael@0: michael@0: TradeGutsReserved(JSContext *cx) michael@0: : avals(cx), bvals(cx), michael@0: newafixed(0), newbfixed(0), michael@0: newashape(cx), newbshape(cx), michael@0: newaslots(nullptr), newbslots(nullptr) michael@0: {} michael@0: michael@0: ~TradeGutsReserved() michael@0: { michael@0: js_free(newaslots); michael@0: js_free(newbslots); michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: JSObject::ReserveForTradeGuts(JSContext *cx, JSObject *aArg, JSObject *bArg, michael@0: TradeGutsReserved &reserved) michael@0: { michael@0: /* michael@0: * Avoid GC in here to avoid confusing the tracing code with our michael@0: * intermediate state. michael@0: */ michael@0: AutoSuppressGC suppress(cx); michael@0: michael@0: RootedObject a(cx, aArg); michael@0: RootedObject b(cx, bArg); michael@0: JS_ASSERT(a->compartment() == b->compartment()); michael@0: AutoCompartment ac(cx, a); michael@0: michael@0: /* michael@0: * When performing multiple swaps between objects which may have different michael@0: * numbers of fixed slots, we reserve all space ahead of time so that the michael@0: * swaps can be performed infallibly. michael@0: */ michael@0: michael@0: /* michael@0: * Swap prototypes and classes on the two objects, so that TradeGuts can michael@0: * preserve the types of the two objects. michael@0: */ michael@0: const Class *aClass = a->getClass(); michael@0: const Class *bClass = b->getClass(); michael@0: Rooted aProto(cx, a->getTaggedProto()); michael@0: Rooted bProto(cx, b->getTaggedProto()); michael@0: bool success; michael@0: if (!SetClassAndProto(cx, a, bClass, bProto, &success) || !success) michael@0: return false; michael@0: if (!SetClassAndProto(cx, b, aClass, aProto, &success) || !success) michael@0: return false; michael@0: michael@0: if (a->tenuredSizeOfThis() == b->tenuredSizeOfThis()) michael@0: return true; michael@0: michael@0: /* michael@0: * If either object is native, it needs a new shape to preserve the michael@0: * invariant that objects with the same shape have the same number of michael@0: * inline slots. The fixed slots will be updated in place during TradeGuts. michael@0: * Non-native objects need to be reshaped according to the new count. michael@0: */ michael@0: if (a->isNative()) { michael@0: if (!a->generateOwnShape(cx)) michael@0: return false; michael@0: } else { michael@0: reserved.newbshape = EmptyShape::getInitialShape(cx, aClass, aProto, a->getParent(), a->getMetadata(), michael@0: b->tenuredGetAllocKind()); michael@0: if (!reserved.newbshape) michael@0: return false; michael@0: } michael@0: if (b->isNative()) { michael@0: if (!b->generateOwnShape(cx)) michael@0: return false; michael@0: } else { michael@0: reserved.newashape = EmptyShape::getInitialShape(cx, bClass, bProto, b->getParent(), b->getMetadata(), michael@0: a->tenuredGetAllocKind()); michael@0: if (!reserved.newashape) michael@0: return false; michael@0: } michael@0: michael@0: /* The avals/bvals vectors hold all original values from the objects. */ michael@0: michael@0: if (!reserved.avals.reserve(a->slotSpan())) michael@0: return false; michael@0: if (!reserved.bvals.reserve(b->slotSpan())) michael@0: return false; michael@0: michael@0: /* michael@0: * The newafixed/newbfixed hold the number of fixed slots in the objects michael@0: * after the swap. Adjust these counts according to whether the objects michael@0: * use their last fixed slot for storing private data. michael@0: */ michael@0: michael@0: reserved.newafixed = a->numFixedSlots(); michael@0: reserved.newbfixed = b->numFixedSlots(); michael@0: michael@0: if (aClass->hasPrivate()) { michael@0: reserved.newafixed++; michael@0: reserved.newbfixed--; michael@0: } michael@0: if (bClass->hasPrivate()) { michael@0: reserved.newbfixed++; michael@0: reserved.newafixed--; michael@0: } michael@0: michael@0: JS_ASSERT(reserved.newafixed >= 0); michael@0: JS_ASSERT(reserved.newbfixed >= 0); michael@0: michael@0: /* michael@0: * The newaslots/newbslots arrays hold any dynamic slots for the objects michael@0: * if they do not have enough fixed slots to accomodate the slots in the michael@0: * other object. michael@0: */ michael@0: michael@0: unsigned adynamic = dynamicSlotsCount(reserved.newafixed, b->slotSpan(), b->getClass()); michael@0: unsigned bdynamic = dynamicSlotsCount(reserved.newbfixed, a->slotSpan(), a->getClass()); michael@0: michael@0: if (adynamic) { michael@0: reserved.newaslots = cx->pod_malloc(adynamic); michael@0: if (!reserved.newaslots) michael@0: return false; michael@0: Debug_SetSlotRangeToCrashOnTouch(reserved.newaslots, adynamic); michael@0: } michael@0: if (bdynamic) { michael@0: reserved.newbslots = cx->pod_malloc(bdynamic); michael@0: if (!reserved.newbslots) michael@0: return false; michael@0: Debug_SetSlotRangeToCrashOnTouch(reserved.newbslots, bdynamic); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved &reserved) michael@0: { michael@0: JS_ASSERT(a->compartment() == b->compartment()); michael@0: JS_ASSERT(a->is() == b->is()); michael@0: michael@0: /* michael@0: * Swap the object's types, to restore their initial type information. michael@0: * The prototypes and classes of the objects were swapped in ReserveForTradeGuts. michael@0: */ michael@0: TypeObject *tmp = a->type_; michael@0: a->type_ = b->type_; michael@0: b->type_ = tmp; michael@0: michael@0: /* Don't try to swap a JSFunction for a plain function JSObject. */ michael@0: JS_ASSERT_IF(a->is(), a->tenuredSizeOfThis() == b->tenuredSizeOfThis()); michael@0: michael@0: /* michael@0: * Regexp guts are more complicated -- we would need to migrate the michael@0: * refcounted JIT code blob for them across compartments instead of just michael@0: * swapping guts. michael@0: */ michael@0: JS_ASSERT(!a->is() && !b->is()); michael@0: michael@0: /* Arrays can use their fixed storage for elements. */ michael@0: JS_ASSERT(!a->is() && !b->is()); michael@0: michael@0: /* michael@0: * Callers should not try to swap ArrayBuffer objects, michael@0: * these use a different slot representation from other objects. michael@0: */ michael@0: JS_ASSERT(!a->is() && !b->is()); michael@0: michael@0: /* Trade the guts of the objects. */ michael@0: const size_t size = a->tenuredSizeOfThis(); michael@0: if (size == b->tenuredSizeOfThis()) { michael@0: /* michael@0: * If the objects are the same size, then we make no assumptions about michael@0: * whether they have dynamically allocated slots and instead just copy michael@0: * them over wholesale. michael@0: */ michael@0: char tmp[mozilla::tl::Max::value]; michael@0: JS_ASSERT(size <= sizeof(tmp)); michael@0: michael@0: js_memcpy(tmp, a, size); michael@0: js_memcpy(a, b, size); michael@0: js_memcpy(b, tmp, size); michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: /* michael@0: * Trigger post barriers for fixed slots. JSObject bits are barriered michael@0: * below, in common with the other case. michael@0: */ michael@0: for (size_t i = 0; i < a->numFixedSlots(); ++i) { michael@0: HeapSlot::writeBarrierPost(cx->runtime(), a, HeapSlot::Slot, i, a->getSlot(i)); michael@0: HeapSlot::writeBarrierPost(cx->runtime(), b, HeapSlot::Slot, i, b->getSlot(i)); michael@0: } michael@0: #endif michael@0: } else { michael@0: /* michael@0: * If the objects are of differing sizes, use the space we reserved michael@0: * earlier to save the slots from each object and then copy them into michael@0: * the new layout for the other object. michael@0: */ michael@0: michael@0: uint32_t acap = a->slotSpan(); michael@0: uint32_t bcap = b->slotSpan(); michael@0: michael@0: for (size_t i = 0; i < acap; i++) michael@0: reserved.avals.infallibleAppend(a->getSlot(i)); michael@0: michael@0: for (size_t i = 0; i < bcap; i++) michael@0: reserved.bvals.infallibleAppend(b->getSlot(i)); michael@0: michael@0: /* Done with the dynamic slots. */ michael@0: if (a->hasDynamicSlots()) michael@0: js_free(a->slots); michael@0: if (b->hasDynamicSlots()) michael@0: js_free(b->slots); michael@0: michael@0: void *apriv = a->hasPrivate() ? a->getPrivate() : nullptr; michael@0: void *bpriv = b->hasPrivate() ? b->getPrivate() : nullptr; michael@0: michael@0: char tmp[sizeof(JSObject)]; michael@0: js_memcpy(&tmp, a, sizeof tmp); michael@0: js_memcpy(a, b, sizeof tmp); michael@0: js_memcpy(b, &tmp, sizeof tmp); michael@0: michael@0: if (a->isNative()) michael@0: a->shape_->setNumFixedSlots(reserved.newafixed); michael@0: else michael@0: a->shape_ = reserved.newashape; michael@0: michael@0: a->slots = reserved.newaslots; michael@0: a->initSlotRange(0, reserved.bvals.begin(), bcap); michael@0: if (a->hasPrivate()) michael@0: a->initPrivate(bpriv); michael@0: michael@0: if (b->isNative()) michael@0: b->shape_->setNumFixedSlots(reserved.newbfixed); michael@0: else michael@0: b->shape_ = reserved.newbshape; michael@0: michael@0: b->slots = reserved.newbslots; michael@0: b->initSlotRange(0, reserved.avals.begin(), acap); michael@0: if (b->hasPrivate()) michael@0: b->initPrivate(apriv); michael@0: michael@0: /* Make sure the destructor for reserved doesn't free the slots. */ michael@0: reserved.newaslots = nullptr; michael@0: reserved.newbslots = nullptr; michael@0: } michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: Shape::writeBarrierPost(a->shape_, &a->shape_); michael@0: Shape::writeBarrierPost(b->shape_, &b->shape_); michael@0: types::TypeObject::writeBarrierPost(a->type_, &a->type_); michael@0: types::TypeObject::writeBarrierPost(b->type_, &b->type_); michael@0: #endif michael@0: michael@0: if (a->inDictionaryMode()) michael@0: a->lastProperty()->listp = &a->shape_; michael@0: if (b->inDictionaryMode()) michael@0: b->lastProperty()->listp = &b->shape_; michael@0: michael@0: #ifdef JSGC_INCREMENTAL michael@0: /* michael@0: * We need a write barrier here. If |a| was marked and |b| was not, then michael@0: * after the swap, |b|'s guts would never be marked. The write barrier michael@0: * solves this. michael@0: * michael@0: * Normally write barriers happen before the write. However, that's not michael@0: * necessary here because nothing is being destroyed. We're just swapping. michael@0: * We don't do the barrier before TradeGuts because ReserveForTradeGuts michael@0: * makes changes to the objects that might confuse the tracing code. michael@0: */ michael@0: JS::Zone *zone = a->zone(); michael@0: if (zone->needsBarrier()) { michael@0: MarkChildren(zone->barrierTracer(), a); michael@0: MarkChildren(zone->barrierTracer(), b); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /* Use this method with extreme caution. It trades the guts of two objects. */ michael@0: bool michael@0: JSObject::swap(JSContext *cx, HandleObject a, HandleObject b) michael@0: { michael@0: AutoMarkInDeadZone adc1(a->zone()); michael@0: AutoMarkInDeadZone adc2(b->zone()); michael@0: michael@0: // Ensure swap doesn't cause a finalizer to not be run. michael@0: JS_ASSERT(IsBackgroundFinalized(a->tenuredGetAllocKind()) == michael@0: IsBackgroundFinalized(b->tenuredGetAllocKind())); michael@0: JS_ASSERT(a->compartment() == b->compartment()); michael@0: michael@0: unsigned r = NotifyGCPreSwap(a, b); michael@0: michael@0: TradeGutsReserved reserved(cx); michael@0: if (!ReserveForTradeGuts(cx, a, b, reserved)) { michael@0: NotifyGCPostSwap(b, a, r); michael@0: return false; michael@0: } michael@0: TradeGuts(cx, a, b, reserved); michael@0: michael@0: NotifyGCPostSwap(a, b, r); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DefineStandardSlot(JSContext *cx, HandleObject obj, JSProtoKey key, JSAtom *atom, michael@0: HandleValue v, uint32_t attrs, bool &named) michael@0: { michael@0: RootedId id(cx, AtomToId(atom)); michael@0: michael@0: if (key != JSProto_Null) { michael@0: /* michael@0: * Initializing an actual standard class on a global object. If the michael@0: * property is not yet present, force it into a new one bound to a michael@0: * reserved slot. Otherwise, go through the normal property path. michael@0: */ michael@0: JS_ASSERT(obj->is()); michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: if (!obj->nativeLookup(cx, id)) { michael@0: obj->as().setConstructorPropertySlot(key, v); michael@0: michael@0: uint32_t slot = GlobalObject::constructorPropertySlot(key); michael@0: if (!JSObject::addProperty(cx, obj, id, JS_PropertyStub, JS_StrictPropertyStub, slot, attrs, 0)) michael@0: return false; michael@0: michael@0: named = true; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: named = JSObject::defineGeneric(cx, obj, id, michael@0: v, JS_PropertyStub, JS_StrictPropertyStub, attrs); michael@0: return named; michael@0: } michael@0: michael@0: static void michael@0: SetClassObject(JSObject *obj, JSProtoKey key, JSObject *cobj, JSObject *proto) michael@0: { michael@0: JS_ASSERT(!obj->getParent()); michael@0: if (!obj->is()) michael@0: return; michael@0: michael@0: obj->as().setConstructor(key, ObjectOrNullValue(cobj)); michael@0: obj->as().setPrototype(key, ObjectOrNullValue(proto)); michael@0: } michael@0: michael@0: static void michael@0: ClearClassObject(JSObject *obj, JSProtoKey key) michael@0: { michael@0: JS_ASSERT(!obj->getParent()); michael@0: if (!obj->is()) michael@0: return; michael@0: michael@0: obj->as().setConstructor(key, UndefinedValue()); michael@0: obj->as().setPrototype(key, UndefinedValue()); michael@0: } michael@0: michael@0: static JSObject * michael@0: DefineConstructorAndPrototype(JSContext *cx, HandleObject obj, JSProtoKey key, HandleAtom atom, michael@0: JSObject *protoProto, const Class *clasp, michael@0: Native constructor, unsigned nargs, michael@0: const JSPropertySpec *ps, const JSFunctionSpec *fs, michael@0: const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, michael@0: JSObject **ctorp, AllocKind ctorKind) michael@0: { michael@0: /* michael@0: * Create a prototype object for this class. michael@0: * michael@0: * FIXME: lazy standard (built-in) class initialization and even older michael@0: * eager boostrapping code rely on all of these properties: michael@0: * michael@0: * 1. NewObject attempting to compute a default prototype object when michael@0: * passed null for proto; and michael@0: * michael@0: * 2. NewObject tolerating no default prototype (null proto slot value) michael@0: * due to this js_InitClass call coming from js_InitFunctionClass on an michael@0: * otherwise-uninitialized global. michael@0: * michael@0: * 3. NewObject allocating a JSFunction-sized GC-thing when clasp is michael@0: * &JSFunction::class_, not a JSObject-sized (smaller) GC-thing. michael@0: * michael@0: * The JS_NewObjectForGivenProto and JS_NewObject APIs also allow clasp to michael@0: * be &JSFunction::class_ (we could break compatibility easily). But michael@0: * fixing (3) is not enough without addressing the bootstrapping dependency michael@0: * on (1) and (2). michael@0: */ michael@0: michael@0: /* michael@0: * Create the prototype object. (GlobalObject::createBlankPrototype isn't michael@0: * used because it parents the prototype object to the global and because michael@0: * it uses WithProto::Given. FIXME: Undo dependencies on this parentage michael@0: * [which already needs to happen for bug 638316], figure out nicer michael@0: * semantics for null-protoProto, and use createBlankPrototype.) michael@0: */ michael@0: RootedObject proto(cx, NewObjectWithClassProto(cx, clasp, protoProto, obj, SingletonObject)); michael@0: if (!proto) michael@0: return nullptr; michael@0: michael@0: /* After this point, control must exit via label bad or out. */ michael@0: RootedObject ctor(cx); michael@0: bool named = false; michael@0: bool cached = false; michael@0: if (!constructor) { michael@0: /* michael@0: * Lacking a constructor, name the prototype (e.g., Math) unless this michael@0: * class (a) is anonymous, i.e. for internal use only; (b) the class michael@0: * of obj (the global object) is has a reserved slot indexed by key; michael@0: * and (c) key is not the null key. michael@0: */ michael@0: if (!(clasp->flags & JSCLASS_IS_ANONYMOUS) || !obj->is() || michael@0: key == JSProto_Null) michael@0: { michael@0: uint32_t attrs = (clasp->flags & JSCLASS_IS_ANONYMOUS) michael@0: ? JSPROP_READONLY | JSPROP_PERMANENT michael@0: : 0; michael@0: RootedValue value(cx, ObjectValue(*proto)); michael@0: if (!DefineStandardSlot(cx, obj, key, atom, value, attrs, named)) michael@0: goto bad; michael@0: } michael@0: michael@0: ctor = proto; michael@0: } else { michael@0: /* michael@0: * Create the constructor, not using GlobalObject::createConstructor michael@0: * because the constructor currently must have |obj| as its parent. michael@0: * (FIXME: remove this dependency on the exact identity of the parent, michael@0: * perhaps as part of bug 638316.) michael@0: */ michael@0: RootedFunction fun(cx, NewFunction(cx, js::NullPtr(), constructor, nargs, michael@0: JSFunction::NATIVE_CTOR, obj, atom, ctorKind)); michael@0: if (!fun) michael@0: goto bad; michael@0: michael@0: /* michael@0: * Set the class object early for standard class constructors. Type michael@0: * inference may need to access these, and js::GetBuiltinPrototype will michael@0: * fail if it tries to do a reentrant reconstruction of the class. michael@0: */ michael@0: if (key != JSProto_Null) { michael@0: SetClassObject(obj, key, fun, proto); michael@0: cached = true; michael@0: } michael@0: michael@0: RootedValue value(cx, ObjectValue(*fun)); michael@0: if (!DefineStandardSlot(cx, obj, key, atom, value, 0, named)) michael@0: goto bad; michael@0: michael@0: /* michael@0: * Optionally construct the prototype object, before the class has michael@0: * been fully initialized. Allow the ctor to replace proto with a michael@0: * different object, as is done for operator new. michael@0: */ michael@0: ctor = fun; michael@0: if (!LinkConstructorAndPrototype(cx, ctor, proto)) michael@0: goto bad; michael@0: michael@0: /* Bootstrap Function.prototype (see also JS_InitStandardClasses). */ michael@0: Rooted tagged(cx, TaggedProto(proto)); michael@0: if (ctor->getClass() == clasp && !ctor->splicePrototype(cx, clasp, tagged)) michael@0: goto bad; michael@0: } michael@0: michael@0: if (!DefinePropertiesAndBrand(cx, proto, ps, fs) || michael@0: (ctor != proto && !DefinePropertiesAndBrand(cx, ctor, static_ps, static_fs))) michael@0: { michael@0: goto bad; michael@0: } michael@0: michael@0: /* If this is a standard class, cache its prototype. */ michael@0: if (!cached && key != JSProto_Null) michael@0: SetClassObject(obj, key, ctor, proto); michael@0: michael@0: if (ctorp) michael@0: *ctorp = ctor; michael@0: return proto; michael@0: michael@0: bad: michael@0: if (named) { michael@0: bool succeeded; michael@0: JSObject::deleteByValue(cx, obj, StringValue(atom), &succeeded); michael@0: } michael@0: if (cached) michael@0: ClearClassObject(obj, key); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSObject * michael@0: js_InitClass(JSContext *cx, HandleObject obj, JSObject *protoProto_, michael@0: const Class *clasp, Native constructor, unsigned nargs, michael@0: const JSPropertySpec *ps, const JSFunctionSpec *fs, michael@0: const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, michael@0: JSObject **ctorp, AllocKind ctorKind) michael@0: { michael@0: RootedObject protoProto(cx, protoProto_); michael@0: michael@0: /* Assert mandatory function pointer members. */ michael@0: JS_ASSERT(clasp->addProperty); michael@0: JS_ASSERT(clasp->delProperty); michael@0: JS_ASSERT(clasp->getProperty); michael@0: JS_ASSERT(clasp->setProperty); michael@0: JS_ASSERT(clasp->enumerate); michael@0: JS_ASSERT(clasp->resolve); michael@0: JS_ASSERT(clasp->convert); michael@0: michael@0: RootedAtom atom(cx, Atomize(cx, clasp->name, strlen(clasp->name))); michael@0: if (!atom) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * All instances of the class will inherit properties from the prototype michael@0: * object we are about to create (in DefineConstructorAndPrototype), which michael@0: * in turn will inherit from protoProto. michael@0: * michael@0: * When initializing a standard class (other than Object), if protoProto is michael@0: * null, default to Object.prototype. The engine's internal uses of michael@0: * js_InitClass depend on this nicety. michael@0: */ michael@0: JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp); michael@0: if (key != JSProto_Null && michael@0: !protoProto && michael@0: !GetBuiltinPrototype(cx, JSProto_Object, &protoProto)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: return DefineConstructorAndPrototype(cx, obj, key, atom, protoProto, clasp, constructor, nargs, michael@0: ps, fs, static_ps, static_fs, ctorp, ctorKind); michael@0: } michael@0: michael@0: /* static */ inline bool michael@0: JSObject::updateSlotsForSpan(ThreadSafeContext *cx, michael@0: HandleObject obj, size_t oldSpan, size_t newSpan) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(oldSpan != newSpan); michael@0: michael@0: size_t oldCount = dynamicSlotsCount(obj->numFixedSlots(), oldSpan, obj->getClass()); michael@0: size_t newCount = dynamicSlotsCount(obj->numFixedSlots(), newSpan, obj->getClass()); michael@0: michael@0: if (oldSpan < newSpan) { michael@0: if (oldCount < newCount && !JSObject::growSlots(cx, obj, oldCount, newCount)) michael@0: return false; michael@0: michael@0: if (newSpan == oldSpan + 1) michael@0: obj->initSlotUnchecked(oldSpan, UndefinedValue()); michael@0: else michael@0: obj->initializeSlotRange(oldSpan, newSpan - oldSpan); michael@0: } else { michael@0: /* Trigger write barriers on the old slots before reallocating. */ michael@0: obj->prepareSlotRangeForOverwrite(newSpan, oldSpan); michael@0: obj->invalidateSlotRange(newSpan, oldSpan - newSpan); michael@0: michael@0: if (oldCount > newCount) michael@0: JSObject::shrinkSlots(cx, obj, oldCount, newCount); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::setLastProperty(ThreadSafeContext *cx, HandleObject obj, HandleShape shape) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(!obj->inDictionaryMode()); michael@0: JS_ASSERT(!shape->inDictionary()); michael@0: JS_ASSERT(shape->compartment() == obj->compartment()); michael@0: JS_ASSERT(shape->numFixedSlots() == obj->numFixedSlots()); michael@0: michael@0: size_t oldSpan = obj->lastProperty()->slotSpan(); michael@0: size_t newSpan = shape->slotSpan(); michael@0: michael@0: if (oldSpan == newSpan) { michael@0: obj->shape_ = shape; michael@0: return true; michael@0: } michael@0: michael@0: if (!updateSlotsForSpan(cx, obj, oldSpan, newSpan)) michael@0: return false; michael@0: michael@0: obj->shape_ = shape; michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::setSlotSpan(ThreadSafeContext *cx, HandleObject obj, uint32_t span) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(obj->inDictionaryMode()); michael@0: michael@0: size_t oldSpan = obj->lastProperty()->base()->slotSpan(); michael@0: if (oldSpan == span) michael@0: return true; michael@0: michael@0: if (!JSObject::updateSlotsForSpan(cx, obj, oldSpan, span)) michael@0: return false; michael@0: michael@0: obj->lastProperty()->base()->setSlotSpan(span); michael@0: return true; michael@0: } michael@0: michael@0: static HeapSlot * michael@0: AllocateSlots(ThreadSafeContext *cx, JSObject *obj, uint32_t nslots) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (cx->isJSContext()) michael@0: return cx->asJSContext()->runtime()->gcNursery.allocateSlots(cx->asJSContext(), obj, nslots); michael@0: #endif michael@0: return cx->pod_malloc(nslots); michael@0: } michael@0: michael@0: static HeapSlot * michael@0: ReallocateSlots(ThreadSafeContext *cx, JSObject *obj, HeapSlot *oldSlots, michael@0: uint32_t oldCount, uint32_t newCount) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (cx->isJSContext()) { michael@0: return cx->asJSContext()->runtime()->gcNursery.reallocateSlots(cx->asJSContext(), michael@0: obj, oldSlots, michael@0: oldCount, newCount); michael@0: } michael@0: #endif michael@0: return (HeapSlot *)cx->realloc_(oldSlots, oldCount * sizeof(HeapSlot), michael@0: newCount * sizeof(HeapSlot)); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::growSlots(ThreadSafeContext *cx, HandleObject obj, uint32_t oldCount, uint32_t newCount) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(newCount > oldCount); michael@0: JS_ASSERT_IF(!obj->is(), newCount >= SLOT_CAPACITY_MIN); michael@0: michael@0: /* michael@0: * Slot capacities are determined by the span of allocated objects. Due to michael@0: * the limited number of bits to store shape slots, object growth is michael@0: * throttled well before the slot capacity can overflow. michael@0: */ michael@0: JS_ASSERT(newCount < NELEMENTS_LIMIT); michael@0: michael@0: /* michael@0: * If we are allocating slots for an object whose type is always created michael@0: * by calling 'new' on a particular script, bump the GC kind for that michael@0: * type to give these objects a larger number of fixed slots when future michael@0: * objects are constructed. michael@0: */ michael@0: if (!obj->hasLazyType() && !oldCount && obj->type()->hasNewScript()) { michael@0: JSObject *oldTemplate = obj->type()->newScript()->templateObject; michael@0: gc::AllocKind kind = gc::GetGCObjectFixedSlotsKind(oldTemplate->numFixedSlots()); michael@0: uint32_t newScriptSlots = gc::GetGCKindSlots(kind); michael@0: if (newScriptSlots == obj->numFixedSlots() && michael@0: gc::TryIncrementAllocKind(&kind) && michael@0: cx->isJSContext()) michael@0: { michael@0: JSContext *ncx = cx->asJSContext(); michael@0: AutoEnterAnalysis enter(ncx); michael@0: michael@0: Rooted typeObj(cx, obj->type()); michael@0: RootedShape shape(cx, oldTemplate->lastProperty()); michael@0: JSObject *reshapedObj = NewReshapedObject(ncx, typeObj, obj->getParent(), kind, shape, michael@0: MaybeSingletonObject); michael@0: if (!reshapedObj) michael@0: return false; michael@0: michael@0: typeObj->newScript()->templateObject = reshapedObj; michael@0: typeObj->markStateChange(ncx); michael@0: } michael@0: } michael@0: michael@0: if (!oldCount) { michael@0: obj->slots = AllocateSlots(cx, obj, newCount); michael@0: if (!obj->slots) michael@0: return false; michael@0: Debug_SetSlotRangeToCrashOnTouch(obj->slots, newCount); michael@0: return true; michael@0: } michael@0: michael@0: HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); michael@0: if (!newslots) michael@0: return false; /* Leave slots at its old size. */ michael@0: michael@0: obj->slots = newslots; michael@0: michael@0: Debug_SetSlotRangeToCrashOnTouch(obj->slots + oldCount, newCount - oldCount); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: FreeSlots(ThreadSafeContext *cx, HeapSlot *slots) michael@0: { michael@0: // Note: threads without a JSContext do not have access to nursery allocated things. michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (cx->isJSContext()) michael@0: return cx->asJSContext()->runtime()->gcNursery.freeSlots(cx->asJSContext(), slots); michael@0: #endif michael@0: js_free(slots); michael@0: } michael@0: michael@0: /* static */ void michael@0: JSObject::shrinkSlots(ThreadSafeContext *cx, HandleObject obj, uint32_t oldCount, uint32_t newCount) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: JS_ASSERT(newCount < oldCount); michael@0: michael@0: if (newCount == 0) { michael@0: FreeSlots(cx, obj->slots); michael@0: obj->slots = nullptr; michael@0: return; michael@0: } michael@0: michael@0: JS_ASSERT_IF(!obj->is(), newCount >= SLOT_CAPACITY_MIN); michael@0: michael@0: HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); michael@0: if (!newslots) michael@0: return; /* Leave slots at its old size. */ michael@0: michael@0: obj->slots = newslots; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::sparsifyDenseElement(ExclusiveContext *cx, HandleObject obj, uint32_t index) michael@0: { michael@0: RootedValue value(cx, obj->getDenseElement(index)); michael@0: JS_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); michael@0: michael@0: JSObject::removeDenseElementForSparseIndex(cx, obj, index); michael@0: michael@0: uint32_t slot = obj->slotSpan(); michael@0: if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) { michael@0: obj->setDenseElement(index, value); michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT(slot == obj->slotSpan() - 1); michael@0: obj->initSlot(slot, value); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::sparsifyDenseElements(js::ExclusiveContext *cx, HandleObject obj) michael@0: { michael@0: uint32_t initialized = obj->getDenseInitializedLength(); michael@0: michael@0: /* Create new properties with the value of non-hole dense elements. */ michael@0: for (uint32_t i = 0; i < initialized; i++) { michael@0: if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) michael@0: continue; michael@0: michael@0: if (!sparsifyDenseElement(cx, obj, i)) michael@0: return false; michael@0: } michael@0: michael@0: if (initialized) michael@0: obj->setDenseInitializedLength(0); michael@0: michael@0: /* michael@0: * Reduce storage for dense elements which are now holes. Explicitly mark michael@0: * the elements capacity as zero, so that any attempts to add dense michael@0: * elements will be caught in ensureDenseElements. michael@0: */ michael@0: if (obj->getDenseCapacity()) { michael@0: obj->shrinkElements(cx, 0); michael@0: obj->getElementsHeader()->capacity = 0; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint) michael@0: { michael@0: JS_ASSERT(isNative()); michael@0: JS_ASSERT(requiredCapacity > MIN_SPARSE_INDEX); michael@0: michael@0: uint32_t cap = getDenseCapacity(); michael@0: JS_ASSERT(requiredCapacity >= cap); michael@0: michael@0: if (requiredCapacity >= NELEMENTS_LIMIT) michael@0: return true; michael@0: michael@0: uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; michael@0: if (newElementsHint >= minimalDenseCount) michael@0: return false; michael@0: minimalDenseCount -= newElementsHint; michael@0: michael@0: if (minimalDenseCount > cap) michael@0: return true; michael@0: michael@0: uint32_t len = getDenseInitializedLength(); michael@0: const Value *elems = getDenseElements(); michael@0: for (uint32_t i = 0; i < len; i++) { michael@0: if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* static */ JSObject::EnsureDenseResult michael@0: JSObject::maybeDensifySparseElements(js::ExclusiveContext *cx, HandleObject obj) michael@0: { michael@0: /* michael@0: * Wait until after the object goes into dictionary mode, which must happen michael@0: * when sparsely packing any array with more than MIN_SPARSE_INDEX elements michael@0: * (see PropertyTree::MAX_HEIGHT). michael@0: */ michael@0: if (!obj->inDictionaryMode()) michael@0: return ED_SPARSE; michael@0: michael@0: /* michael@0: * Only measure the number of indexed properties every log(n) times when michael@0: * populating the object. michael@0: */ michael@0: uint32_t slotSpan = obj->slotSpan(); michael@0: if (slotSpan != RoundUpPow2(slotSpan)) michael@0: return ED_SPARSE; michael@0: michael@0: /* Watch for conditions under which an object's elements cannot be dense. */ michael@0: if (!obj->nonProxyIsExtensible() || obj->watched()) michael@0: return ED_SPARSE; michael@0: michael@0: /* michael@0: * The indexes in the object need to be sufficiently dense before they can michael@0: * be converted to dense mode. michael@0: */ michael@0: uint32_t numDenseElements = 0; michael@0: uint32_t newInitializedLength = 0; michael@0: michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: while (!shape->isEmptyShape()) { michael@0: uint32_t index; michael@0: if (js_IdIsIndex(shape->propid(), &index)) { michael@0: if (shape->attributes() == JSPROP_ENUMERATE && michael@0: shape->hasDefaultGetter() && michael@0: shape->hasDefaultSetter()) michael@0: { michael@0: numDenseElements++; michael@0: newInitializedLength = Max(newInitializedLength, index + 1); michael@0: } else { michael@0: /* michael@0: * For simplicity, only densify the object if all indexed michael@0: * properties can be converted to dense elements. michael@0: */ michael@0: return ED_SPARSE; michael@0: } michael@0: } michael@0: shape = shape->previous(); michael@0: } michael@0: michael@0: if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) michael@0: return ED_SPARSE; michael@0: michael@0: if (newInitializedLength >= NELEMENTS_LIMIT) michael@0: return ED_SPARSE; michael@0: michael@0: /* michael@0: * This object meets all necessary restrictions, convert all indexed michael@0: * properties into dense elements. michael@0: */ michael@0: michael@0: if (newInitializedLength > obj->getDenseCapacity()) { michael@0: if (!obj->growElements(cx, newInitializedLength)) michael@0: return ED_FAILED; michael@0: } michael@0: michael@0: obj->ensureDenseInitializedLength(cx, newInitializedLength, 0); michael@0: michael@0: RootedValue value(cx); michael@0: michael@0: shape = obj->lastProperty(); michael@0: while (!shape->isEmptyShape()) { michael@0: jsid id = shape->propid(); michael@0: uint32_t index; michael@0: if (js_IdIsIndex(id, &index)) { michael@0: value = obj->getSlot(shape->slot()); michael@0: michael@0: /* michael@0: * When removing a property from a dictionary, the specified michael@0: * property will be removed from the dictionary list and the michael@0: * last property will then be changed due to reshaping the object. michael@0: * Compute the next shape in the traverse, watching for such michael@0: * removals from the list. michael@0: */ michael@0: if (shape != obj->lastProperty()) { michael@0: shape = shape->previous(); michael@0: if (!obj->removeProperty(cx, id)) michael@0: return ED_FAILED; michael@0: } else { michael@0: if (!obj->removeProperty(cx, id)) michael@0: return ED_FAILED; michael@0: shape = obj->lastProperty(); michael@0: } michael@0: michael@0: obj->setDenseElement(index, value); michael@0: } else { michael@0: shape = shape->previous(); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * All indexed properties on the object are now dense, clear the indexed michael@0: * flag so that we will not start using sparse indexes again if we need michael@0: * to grow the object. michael@0: */ michael@0: if (!obj->clearFlag(cx, BaseShape::INDEXED)) michael@0: return ED_FAILED; michael@0: michael@0: return ED_OK; michael@0: } michael@0: michael@0: static ObjectElements * michael@0: AllocateElements(ThreadSafeContext *cx, JSObject *obj, uint32_t nelems) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (cx->isJSContext()) michael@0: return cx->asJSContext()->runtime()->gcNursery.allocateElements(cx->asJSContext(), obj, nelems); michael@0: #endif michael@0: michael@0: return static_cast(cx->malloc_(nelems * sizeof(HeapValue))); michael@0: } michael@0: michael@0: static ObjectElements * michael@0: ReallocateElements(ThreadSafeContext *cx, JSObject *obj, ObjectElements *oldHeader, michael@0: uint32_t oldCount, uint32_t newCount) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (cx->isJSContext()) { michael@0: return cx->asJSContext()->runtime()->gcNursery.reallocateElements(cx->asJSContext(), obj, michael@0: oldHeader, oldCount, michael@0: newCount); michael@0: } michael@0: #endif michael@0: michael@0: return static_cast(cx->realloc_(oldHeader, oldCount * sizeof(HeapSlot), michael@0: newCount * sizeof(HeapSlot))); michael@0: } michael@0: michael@0: bool michael@0: JSObject::growElements(ThreadSafeContext *cx, uint32_t newcap) michael@0: { michael@0: JS_ASSERT(nonProxyIsExtensible()); michael@0: JS_ASSERT(canHaveNonEmptyElements()); michael@0: michael@0: /* michael@0: * When an object with CAPACITY_DOUBLING_MAX or fewer elements needs to michael@0: * grow, double its capacity, to add N elements in amortized O(N) time. michael@0: * michael@0: * Above this limit, grow by 12.5% each time. Speed is still amortized michael@0: * O(N), with a higher constant factor, and we waste less space. michael@0: */ michael@0: static const size_t CAPACITY_DOUBLING_MAX = 1024 * 1024; michael@0: static const size_t CAPACITY_CHUNK = CAPACITY_DOUBLING_MAX / sizeof(Value); michael@0: michael@0: uint32_t oldcap = getDenseCapacity(); michael@0: JS_ASSERT(oldcap <= newcap); michael@0: michael@0: uint32_t nextsize = (oldcap <= CAPACITY_DOUBLING_MAX) michael@0: ? oldcap * 2 michael@0: : oldcap + (oldcap >> 3); michael@0: michael@0: uint32_t actualCapacity; michael@0: if (is() && !as().lengthIsWritable()) { michael@0: JS_ASSERT(newcap <= as().length()); michael@0: // Preserve the |capacity <= length| invariant for arrays with michael@0: // non-writable length. See also js::ArraySetLength which initially michael@0: // enforces this requirement. michael@0: actualCapacity = newcap; michael@0: } else { michael@0: actualCapacity = Max(newcap, nextsize); michael@0: if (actualCapacity >= CAPACITY_CHUNK) michael@0: actualCapacity = JS_ROUNDUP(actualCapacity, CAPACITY_CHUNK); michael@0: else if (actualCapacity < SLOT_CAPACITY_MIN) michael@0: actualCapacity = SLOT_CAPACITY_MIN; michael@0: michael@0: /* Don't let nelements get close to wrapping around uint32_t. */ michael@0: if (actualCapacity >= NELEMENTS_LIMIT || actualCapacity < oldcap || actualCapacity < newcap) michael@0: return false; michael@0: } michael@0: michael@0: uint32_t initlen = getDenseInitializedLength(); michael@0: uint32_t oldAllocated = oldcap + ObjectElements::VALUES_PER_HEADER; michael@0: uint32_t newAllocated = actualCapacity + ObjectElements::VALUES_PER_HEADER; michael@0: michael@0: ObjectElements *newheader; michael@0: if (hasDynamicElements()) { michael@0: newheader = ReallocateElements(cx, this, getElementsHeader(), oldAllocated, newAllocated); michael@0: if (!newheader) michael@0: return false; /* Leave elements as its old size. */ michael@0: } else { michael@0: newheader = AllocateElements(cx, this, newAllocated); michael@0: if (!newheader) michael@0: return false; /* Leave elements as its old size. */ michael@0: js_memcpy(newheader, getElementsHeader(), michael@0: (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value)); michael@0: } michael@0: michael@0: newheader->capacity = actualCapacity; michael@0: elements = newheader->elements(); michael@0: michael@0: Debug_SetSlotRangeToCrashOnTouch(elements + initlen, actualCapacity - initlen); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: JSObject::shrinkElements(ThreadSafeContext *cx, uint32_t newcap) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(this)); michael@0: JS_ASSERT(canHaveNonEmptyElements()); michael@0: michael@0: uint32_t oldcap = getDenseCapacity(); michael@0: JS_ASSERT(newcap <= oldcap); michael@0: michael@0: // Don't shrink elements below the minimum capacity. michael@0: if (oldcap <= SLOT_CAPACITY_MIN || !hasDynamicElements()) michael@0: return; michael@0: michael@0: newcap = Max(newcap, SLOT_CAPACITY_MIN); michael@0: michael@0: uint32_t oldAllocated = oldcap + ObjectElements::VALUES_PER_HEADER; michael@0: uint32_t newAllocated = newcap + ObjectElements::VALUES_PER_HEADER; michael@0: michael@0: ObjectElements *newheader = ReallocateElements(cx, this, getElementsHeader(), michael@0: oldAllocated, newAllocated); michael@0: if (!newheader) { michael@0: cx->recoverFromOutOfMemory(); michael@0: return; // Leave elements at its old size. michael@0: } michael@0: michael@0: newheader->capacity = newcap; michael@0: elements = newheader->elements(); michael@0: } michael@0: michael@0: bool michael@0: js::SetClassAndProto(JSContext *cx, HandleObject obj, michael@0: const Class *clasp, Handle proto, michael@0: bool *succeeded) michael@0: { michael@0: /* michael@0: * Regenerate shapes for all of the scopes along the old prototype chain, michael@0: * in case any entries were filled by looking up through obj. Stop when a michael@0: * non-native object is found, prototype lookups will not be cached across michael@0: * these. michael@0: * michael@0: * How this shape change is done is very delicate; the change can be made michael@0: * either by marking the object's prototype as uncacheable (such that the michael@0: * property cache and JIT'ed ICs cannot assume the shape determines the michael@0: * prototype) or by just generating a new shape for the object. Choosing michael@0: * the former is bad if the object is on the prototype chain of other michael@0: * objects, as the uncacheable prototype can inhibit iterator caches on michael@0: * those objects and slow down prototype accesses. Choosing the latter is michael@0: * bad if there are many similar objects to this one which will have their michael@0: * prototype mutated, as the generateOwnShape forces the object into michael@0: * dictionary mode and similar property lineages will be repeatedly cloned. michael@0: * michael@0: * :XXX: bug 707717 make this code less brittle. michael@0: */ michael@0: *succeeded = false; michael@0: RootedObject oldproto(cx, obj); michael@0: while (oldproto && oldproto->isNative()) { michael@0: if (oldproto->hasSingletonType()) { michael@0: if (!oldproto->generateOwnShape(cx)) michael@0: return false; michael@0: } else { michael@0: if (!oldproto->setUncacheableProto(cx)) michael@0: return false; michael@0: } michael@0: oldproto = oldproto->getProto(); michael@0: } michael@0: michael@0: if (obj->hasSingletonType()) { michael@0: /* michael@0: * Just splice the prototype, but mark the properties as unknown for michael@0: * consistent behavior. michael@0: */ michael@0: if (!obj->splicePrototype(cx, clasp, proto)) michael@0: return false; michael@0: MarkTypeObjectUnknownProperties(cx, obj->type()); michael@0: *succeeded = true; michael@0: return true; michael@0: } michael@0: michael@0: if (proto.isObject()) { michael@0: RootedObject protoObj(cx, proto.toObject()); michael@0: if (!JSObject::setNewTypeUnknown(cx, clasp, protoObj)) michael@0: return false; michael@0: } michael@0: michael@0: TypeObject *type = cx->getNewType(clasp, proto); michael@0: if (!type) michael@0: return false; michael@0: michael@0: /* michael@0: * Setting __proto__ on an object that has escaped and may be referenced by michael@0: * other heap objects can only be done if the properties of both objects michael@0: * are unknown. Type sets containing this object will contain the original michael@0: * type but not the new type of the object, so we need to go and scan the michael@0: * entire compartment for type sets which have these objects and mark them michael@0: * as containing generic objects. michael@0: */ michael@0: MarkTypeObjectUnknownProperties(cx, obj->type(), true); michael@0: MarkTypeObjectUnknownProperties(cx, type, true); michael@0: michael@0: obj->setType(type); michael@0: michael@0: *succeeded = true; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: MaybeResolveConstructor(ExclusiveContext *cxArg, Handle global, JSProtoKey key) michael@0: { michael@0: if (global->isStandardClassResolved(key)) michael@0: return true; michael@0: if (!cxArg->shouldBeJSContext()) michael@0: return false; michael@0: michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: return GlobalObject::resolveConstructor(cx, global, key); michael@0: } michael@0: michael@0: bool michael@0: js::GetBuiltinConstructor(ExclusiveContext *cx, JSProtoKey key, MutableHandleObject objp) michael@0: { michael@0: MOZ_ASSERT(key != JSProto_Null); michael@0: Rooted global(cx, cx->global()); michael@0: if (!MaybeResolveConstructor(cx, global, key)) michael@0: return false; michael@0: michael@0: objp.set(&global->getConstructor(key).toObject()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::GetBuiltinPrototype(ExclusiveContext *cx, JSProtoKey key, MutableHandleObject protop) michael@0: { michael@0: MOZ_ASSERT(key != JSProto_Null); michael@0: Rooted global(cx, cx->global()); michael@0: if (!MaybeResolveConstructor(cx, global, key)) michael@0: return false; michael@0: michael@0: protop.set(&global->getPrototype(key).toObject()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsStandardPrototype(JSObject *obj, JSProtoKey key) michael@0: { michael@0: GlobalObject &global = obj->global(); michael@0: Value v = global.getPrototype(key); michael@0: return v.isObject() && obj == &v.toObject(); michael@0: } michael@0: michael@0: JSProtoKey michael@0: JS::IdentifyStandardInstance(JSObject *obj) michael@0: { michael@0: // Note: The prototype shares its JSClass with instances. michael@0: JS_ASSERT(!obj->is()); michael@0: JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass()); michael@0: if (key != JSProto_Null && !IsStandardPrototype(obj, key)) michael@0: return key; michael@0: return JSProto_Null; michael@0: } michael@0: michael@0: JSProtoKey michael@0: JS::IdentifyStandardPrototype(JSObject *obj) michael@0: { michael@0: // Note: The prototype shares its JSClass with instances. michael@0: JS_ASSERT(!obj->is()); michael@0: JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass()); michael@0: if (key != JSProto_Null && IsStandardPrototype(obj, key)) michael@0: return key; michael@0: return JSProto_Null; michael@0: } michael@0: michael@0: JSProtoKey michael@0: JS::IdentifyStandardInstanceOrPrototype(JSObject *obj) michael@0: { michael@0: return JSCLASS_CACHED_PROTO_KEY(obj->getClass()); michael@0: } michael@0: michael@0: bool michael@0: js::FindClassObject(ExclusiveContext *cx, MutableHandleObject protop, const Class *clasp) michael@0: { michael@0: JSProtoKey protoKey = GetClassProtoKey(clasp); michael@0: if (protoKey != JSProto_Null) { michael@0: JS_ASSERT(JSProto_Null < protoKey); michael@0: JS_ASSERT(protoKey < JSProto_LIMIT); michael@0: return GetBuiltinConstructor(cx, protoKey, protop); michael@0: } michael@0: michael@0: JSAtom *atom = Atomize(cx, clasp->name, strlen(clasp->name)); michael@0: if (!atom) michael@0: return false; michael@0: RootedId id(cx, AtomToId(atom)); michael@0: michael@0: RootedObject pobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!LookupNativeProperty(cx, cx->global(), id, &pobj, &shape)) michael@0: return false; michael@0: RootedValue v(cx); michael@0: if (shape && pobj->isNative()) { michael@0: if (shape->hasSlot()) michael@0: v = pobj->nativeGetSlot(shape->slot()); michael@0: } michael@0: if (v.isObject()) michael@0: protop.set(&v.toObject()); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::allocSlot(ThreadSafeContext *cx, HandleObject obj, uint32_t *slotp) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: michael@0: uint32_t slot = obj->slotSpan(); michael@0: JS_ASSERT(slot >= JSSLOT_FREE(obj->getClass())); michael@0: michael@0: /* michael@0: * If this object is in dictionary mode, try to pull a free slot from the michael@0: * shape table's slot-number freelist. michael@0: */ michael@0: if (obj->inDictionaryMode()) { michael@0: ShapeTable &table = obj->lastProperty()->table(); michael@0: uint32_t last = table.freelist; michael@0: if (last != SHAPE_INVALID_SLOT) { michael@0: #ifdef DEBUG michael@0: JS_ASSERT(last < slot); michael@0: uint32_t next = obj->getSlot(last).toPrivateUint32(); michael@0: JS_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot); michael@0: #endif michael@0: michael@0: *slotp = last; michael@0: michael@0: const Value &vref = obj->getSlot(last); michael@0: table.freelist = vref.toPrivateUint32(); michael@0: obj->setSlot(last, UndefinedValue()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (slot >= SHAPE_MAXIMUM_SLOT) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: *slotp = slot; michael@0: michael@0: if (obj->inDictionaryMode() && !setSlotSpan(cx, obj, slot + 1)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: JSObject::freeSlot(uint32_t slot) michael@0: { michael@0: JS_ASSERT(slot < slotSpan()); michael@0: michael@0: if (inDictionaryMode()) { michael@0: uint32_t &last = lastProperty()->table().freelist; michael@0: michael@0: /* Can't afford to check the whole freelist, but let's check the head. */ michael@0: JS_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot); michael@0: michael@0: /* michael@0: * Place all freed slots other than reserved slots (bug 595230) on the michael@0: * dictionary's free list. michael@0: */ michael@0: if (JSSLOT_FREE(getClass()) <= slot) { michael@0: JS_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan()); michael@0: setSlot(slot, PrivateUint32Value(last)); michael@0: last = slot; michael@0: return; michael@0: } michael@0: } michael@0: setSlot(slot, UndefinedValue()); michael@0: } michael@0: michael@0: static bool michael@0: PurgeProtoChain(ExclusiveContext *cx, JSObject *objArg, HandleId id) michael@0: { michael@0: /* Root locally so we can re-assign. */ michael@0: RootedObject obj(cx, objArg); michael@0: michael@0: RootedShape shape(cx); michael@0: while (obj) { michael@0: /* Lookups will not be cached through non-native protos. */ michael@0: if (!obj->isNative()) michael@0: break; michael@0: michael@0: shape = obj->nativeLookup(cx, id); michael@0: if (shape) { michael@0: if (!obj->shadowingShapeChange(cx, *shape)) michael@0: return false; michael@0: michael@0: obj->shadowingShapeChange(cx, *shape); michael@0: return true; michael@0: } michael@0: obj = obj->getProto(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PurgeScopeChainHelper(ExclusiveContext *cx, HandleObject objArg, HandleId id) michael@0: { michael@0: /* Re-root locally so we can re-assign. */ michael@0: RootedObject obj(cx, objArg); michael@0: michael@0: JS_ASSERT(obj->isNative()); michael@0: JS_ASSERT(obj->isDelegate()); michael@0: michael@0: /* Lookups on integer ids cannot be cached through prototypes. */ michael@0: if (JSID_IS_INT(id)) michael@0: return true; michael@0: michael@0: PurgeProtoChain(cx, obj->getProto(), id); michael@0: michael@0: /* michael@0: * We must purge the scope chain only for Call objects as they are the only michael@0: * kind of cacheable non-global object that can gain properties after outer michael@0: * properties with the same names have been cached or traced. Call objects michael@0: * may gain such properties via eval introducing new vars; see bug 490364. michael@0: */ michael@0: if (obj->is()) { michael@0: while ((obj = obj->enclosingScope()) != nullptr) { michael@0: if (!PurgeProtoChain(cx, obj, id)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * PurgeScopeChain does nothing if obj is not itself a prototype or parent michael@0: * scope, else it reshapes the scope and prototype chains it links. It calls michael@0: * PurgeScopeChainHelper, which asserts that obj is flagged as a delegate michael@0: * (i.e., obj has ever been on a prototype or parent chain). michael@0: */ michael@0: static inline bool michael@0: PurgeScopeChain(ExclusiveContext *cx, JS::HandleObject obj, JS::HandleId id) michael@0: { michael@0: if (obj->isDelegate()) michael@0: return PurgeScopeChainHelper(cx, obj, id); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: baseops::DefineGeneric(ExclusiveContext *cx, HandleObject obj, HandleId id, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::defineGeneric(ExclusiveContext *cx, HandleObject obj, michael@0: HandleId id, HandleValue value, michael@0: JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: JS_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); michael@0: js::DefineGenericOp op = obj->getOps()->defineGeneric; michael@0: if (op) { michael@0: if (!cx->shouldBeJSContext()) michael@0: return false; michael@0: return op(cx->asJSContext(), obj, id, value, getter, setter, attrs); michael@0: } michael@0: return baseops::DefineGeneric(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::defineProperty(ExclusiveContext *cx, HandleObject obj, michael@0: PropertyName *name, HandleValue value, michael@0: JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: return defineGeneric(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: bool michael@0: baseops::DefineElement(ExclusiveContext *cx, HandleObject obj, uint32_t index, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: RootedId id(cx); michael@0: if (index <= JSID_INT_MAX) { michael@0: id = INT_TO_JSID(index); michael@0: return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); michael@0: michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: michael@0: return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSObject::defineElement(ExclusiveContext *cx, HandleObject obj, michael@0: uint32_t index, HandleValue value, michael@0: JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: js::DefineElementOp op = obj->getOps()->defineElement; michael@0: if (op) { michael@0: if (!cx->shouldBeJSContext()) michael@0: return false; michael@0: return op(cx->asJSContext(), obj, index, value, getter, setter, attrs); michael@0: } michael@0: return baseops::DefineElement(cx, obj, index, value, getter, setter, attrs); michael@0: } michael@0: michael@0: Shape * michael@0: JSObject::addDataProperty(ExclusiveContext *cx, jsid idArg, uint32_t slot, unsigned attrs) michael@0: { michael@0: JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); michael@0: RootedObject self(cx, this); michael@0: RootedId id(cx, idArg); michael@0: return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); michael@0: } michael@0: michael@0: Shape * michael@0: JSObject::addDataProperty(ExclusiveContext *cx, HandlePropertyName name, michael@0: uint32_t slot, unsigned attrs) michael@0: { michael@0: JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); michael@0: RootedObject self(cx, this); michael@0: RootedId id(cx, NameToId(name)); michael@0: return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); michael@0: } michael@0: michael@0: /* michael@0: * Backward compatibility requires allowing addProperty hooks to mutate the michael@0: * nominal initial value of a slotful property, while GC safety wants that michael@0: * value to be stored before the call-out through the hook. Optimize to do michael@0: * both while saving cycles for classes that stub their addProperty hook. michael@0: */ michael@0: template michael@0: static inline bool michael@0: CallAddPropertyHook(typename ExecutionModeTraits::ExclusiveContextType cxArg, michael@0: const Class *clasp, HandleObject obj, HandleShape shape, michael@0: HandleValue nominal) michael@0: { michael@0: if (clasp->addProperty != JS_PropertyStub) { michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: ExclusiveContext *cx = cxArg->asExclusiveContext(); michael@0: if (!cx->shouldBeJSContext()) michael@0: return false; michael@0: michael@0: /* Make a local copy of value so addProperty can mutate its inout parameter. */ michael@0: RootedValue value(cx, nominal); michael@0: michael@0: Rooted id(cx, shape->propid()); michael@0: if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { michael@0: obj->removeProperty(cx, shape->propid()); michael@0: return false; michael@0: } michael@0: if (value.get() != nominal) { michael@0: if (shape->hasSlot()) michael@0: obj->nativeSetSlotWithType(cx, shape, value); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static inline bool michael@0: CallAddPropertyHookDense(typename ExecutionModeTraits::ExclusiveContextType cxArg, michael@0: const Class *clasp, HandleObject obj, uint32_t index, michael@0: HandleValue nominal) michael@0: { michael@0: /* Inline addProperty for array objects. */ michael@0: if (obj->is()) { michael@0: ArrayObject *arr = &obj->as(); michael@0: uint32_t length = arr->length(); michael@0: if (index >= length) { michael@0: if (mode == ParallelExecution) { michael@0: /* We cannot deal with overflows in parallel. */ michael@0: if (length > INT32_MAX) michael@0: return false; michael@0: arr->setLengthInt32(index + 1); michael@0: } else { michael@0: arr->setLength(cxArg->asExclusiveContext(), index + 1); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: if (clasp->addProperty != JS_PropertyStub) { michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: ExclusiveContext *cx = cxArg->asExclusiveContext(); michael@0: if (!cx->shouldBeJSContext()) michael@0: return false; michael@0: michael@0: /* Make a local copy of value so addProperty can mutate its inout parameter. */ michael@0: RootedValue value(cx, nominal); michael@0: michael@0: Rooted id(cx, INT_TO_JSID(index)); michael@0: if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { michael@0: obj->setDenseElementHole(cx, index); michael@0: return false; michael@0: } michael@0: if (value.get() != nominal) michael@0: obj->setDenseElementWithType(cx, index, value); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: UpdateShapeTypeAndValue(typename ExecutionModeTraits::ExclusiveContextType cx, michael@0: JSObject *obj, Shape *shape, const Value &value) michael@0: { michael@0: jsid id = shape->propid(); michael@0: if (shape->hasSlot()) { michael@0: if (mode == ParallelExecution) { michael@0: if (!obj->nativeSetSlotIfHasType(shape, value)) michael@0: return false; michael@0: } else { michael@0: obj->nativeSetSlotWithType(cx->asExclusiveContext(), shape, value); michael@0: } michael@0: } michael@0: if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter()) { michael@0: if (mode == ParallelExecution) { michael@0: if (!IsTypePropertyIdMarkedNonData(obj, id)) michael@0: return false; michael@0: } else { michael@0: MarkTypePropertyNonData(cx->asExclusiveContext(), obj, id); michael@0: } michael@0: } michael@0: if (!shape->writable()) { michael@0: if (mode == ParallelExecution) { michael@0: if (!IsTypePropertyIdMarkedNonWritable(obj, id)) michael@0: return false; michael@0: } else { michael@0: MarkTypePropertyNonWritable(cx->asExclusiveContext(), obj, id); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static inline bool michael@0: DefinePropertyOrElement(typename ExecutionModeTraits::ExclusiveContextType cx, michael@0: HandleObject obj, HandleId id, michael@0: PropertyOp getter, StrictPropertyOp setter, michael@0: unsigned attrs, HandleValue value, michael@0: bool callSetterAfterwards, bool setterIsStrict) michael@0: { michael@0: /* Use dense storage for new indexed properties where possible. */ michael@0: if (JSID_IS_INT(id) && michael@0: getter == JS_PropertyStub && michael@0: setter == JS_StrictPropertyStub && michael@0: attrs == JSPROP_ENUMERATE && michael@0: (!obj->isIndexed() || !obj->nativeContainsPure(id)) && michael@0: !obj->is()) michael@0: { michael@0: uint32_t index = JSID_TO_INT(id); michael@0: bool definesPast; michael@0: if (!WouldDefinePastNonwritableLength(cx, obj, index, setterIsStrict, &definesPast)) michael@0: return false; michael@0: if (definesPast) michael@0: return true; michael@0: michael@0: JSObject::EnsureDenseResult result; michael@0: if (mode == ParallelExecution) { michael@0: if (obj->writeToIndexWouldMarkNotPacked(index)) michael@0: return false; michael@0: result = obj->ensureDenseElementsPreservePackedFlag(cx, index, 1); michael@0: } else { michael@0: result = obj->ensureDenseElements(cx->asExclusiveContext(), index, 1); michael@0: } michael@0: michael@0: if (result == JSObject::ED_FAILED) michael@0: return false; michael@0: if (result == JSObject::ED_OK) { michael@0: if (mode == ParallelExecution) { michael@0: if (!obj->setDenseElementIfHasType(index, value)) michael@0: return false; michael@0: } else { michael@0: obj->setDenseElementWithType(cx->asExclusiveContext(), index, value); michael@0: } michael@0: return CallAddPropertyHookDense(cx, obj->getClass(), obj, index, value); michael@0: } michael@0: } michael@0: michael@0: if (obj->is()) { michael@0: Rooted arr(cx, &obj->as()); michael@0: if (id == NameToId(cx->names().length)) { michael@0: if (mode == SequentialExecution && !cx->shouldBeJSContext()) michael@0: return false; michael@0: return ArraySetLength(ExecutionModeTraits::toContextType(cx), arr, id, michael@0: attrs, value, setterIsStrict); michael@0: } michael@0: michael@0: uint32_t index; michael@0: if (js_IdIsIndex(id, &index)) { michael@0: bool definesPast; michael@0: if (!WouldDefinePastNonwritableLength(cx, arr, index, setterIsStrict, &definesPast)) michael@0: return false; michael@0: if (definesPast) michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Don't define new indexed properties on typed arrays. michael@0: if (obj->is()) { michael@0: uint64_t index; michael@0: if (IsTypedArrayIndex(id, &index)) michael@0: return true; michael@0: } michael@0: michael@0: AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); michael@0: michael@0: RootedShape shape(cx, JSObject::putProperty(cx, obj, id, getter, setter, michael@0: SHAPE_INVALID_SLOT, attrs, 0)); michael@0: if (!shape) michael@0: return false; michael@0: michael@0: if (!UpdateShapeTypeAndValue(cx, obj, shape, value)) michael@0: return false; michael@0: michael@0: /* michael@0: * Clear any existing dense index after adding a sparse indexed property, michael@0: * and investigate converting the object to dense indexes. michael@0: */ michael@0: if (JSID_IS_INT(id)) { michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: ExclusiveContext *ncx = cx->asExclusiveContext(); michael@0: uint32_t index = JSID_TO_INT(id); michael@0: JSObject::removeDenseElementForSparseIndex(ncx, obj, index); michael@0: JSObject::EnsureDenseResult result = JSObject::maybeDensifySparseElements(ncx, obj); michael@0: if (result == JSObject::ED_FAILED) michael@0: return false; michael@0: if (result == JSObject::ED_OK) { michael@0: JS_ASSERT(setter == JS_StrictPropertyStub); michael@0: return CallAddPropertyHookDense(cx, obj->getClass(), obj, index, value); michael@0: } michael@0: } michael@0: michael@0: if (!CallAddPropertyHook(cx, obj->getClass(), obj, shape, value)) michael@0: return false; michael@0: michael@0: if (callSetterAfterwards && setter != JS_StrictPropertyStub) { michael@0: if (!cx->shouldBeJSContext()) michael@0: return false; michael@0: RootedValue nvalue(cx, value); michael@0: return NativeSet(ExecutionModeTraits::toContextType(cx), michael@0: obj, obj, shape, setterIsStrict, &nvalue); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NativeLookupOwnProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandle shapep); michael@0: michael@0: bool michael@0: js::DefineNativeProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, HandleValue value, michael@0: PropertyOp getter, StrictPropertyOp setter, unsigned attrs) michael@0: { michael@0: JS_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); michael@0: michael@0: AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); michael@0: michael@0: /* michael@0: * If defining a getter or setter, we must check for its counterpart and michael@0: * update the attributes and property ops. A getter or setter is really michael@0: * only half of a property. michael@0: */ michael@0: RootedShape shape(cx); michael@0: if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { michael@0: /* michael@0: * If we are defining a getter whose setter was already defined, or michael@0: * vice versa, finish the job via obj->changeProperty. michael@0: */ michael@0: if (!NativeLookupOwnProperty(cx, obj, id, &shape)) michael@0: return false; michael@0: if (shape) { michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: if (obj->is()) { michael@0: /* Ignore getter/setter properties added to typed arrays. */ michael@0: return true; michael@0: } michael@0: if (!JSObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id))) michael@0: return false; michael@0: shape = obj->nativeLookup(cx, id); michael@0: } michael@0: if (shape->isAccessorDescriptor()) { michael@0: shape = JSObject::changeProperty(cx, obj, shape, attrs, michael@0: JSPROP_GETTER | JSPROP_SETTER, michael@0: (attrs & JSPROP_GETTER) michael@0: ? getter michael@0: : shape->getter(), michael@0: (attrs & JSPROP_SETTER) michael@0: ? setter michael@0: : shape->setter()); michael@0: if (!shape) michael@0: return false; michael@0: } else { michael@0: shape = nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Purge the property cache of any properties named by id that are about michael@0: * to be shadowed in obj's scope chain. michael@0: */ michael@0: if (!PurgeScopeChain(cx, obj, id)) michael@0: return false; michael@0: michael@0: /* Use the object's class getter and setter by default. */ michael@0: const Class *clasp = obj->getClass(); michael@0: if (!getter && !(attrs & JSPROP_GETTER)) michael@0: getter = clasp->getProperty; michael@0: if (!setter && !(attrs & JSPROP_SETTER)) michael@0: setter = clasp->setProperty; michael@0: michael@0: if (!shape) { michael@0: return DefinePropertyOrElement(cx, obj, id, getter, setter, michael@0: attrs, value, false, false); michael@0: } michael@0: michael@0: JS_ALWAYS_TRUE(UpdateShapeTypeAndValue(cx, obj, shape, value)); michael@0: michael@0: return CallAddPropertyHook(cx, clasp, obj, shape, value); michael@0: } michael@0: michael@0: /* michael@0: * Call obj's resolve hook. michael@0: * michael@0: * cx, id, and flags are the parameters initially passed to the ongoing lookup; michael@0: * objp and propp are its out parameters. obj is an object along the prototype michael@0: * chain from where the lookup started. michael@0: * michael@0: * There are four possible outcomes: michael@0: * michael@0: * - On failure, report an error or exception and return false. michael@0: * michael@0: * - If we are already resolving a property of *curobjp, set *recursedp = true, michael@0: * and return true. michael@0: * michael@0: * - If the resolve hook finds or defines the sought property, set *objp and michael@0: * *propp appropriately, set *recursedp = false, and return true. michael@0: * michael@0: * - Otherwise no property was resolved. Set *propp = nullptr and michael@0: * *recursedp = false and return true. michael@0: */ michael@0: static MOZ_ALWAYS_INLINE bool michael@0: CallResolveOp(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp, michael@0: MutableHandleShape propp, bool *recursedp) michael@0: { michael@0: const Class *clasp = obj->getClass(); michael@0: JSResolveOp resolve = clasp->resolve; michael@0: michael@0: /* michael@0: * Avoid recursion on (obj, id) already being resolved on cx. michael@0: * michael@0: * Once we have successfully added an entry for (obj, key) to michael@0: * cx->resolvingTable, control must go through cleanup: before michael@0: * returning. But note that JS_DHASH_ADD may find an existing michael@0: * entry, in which case we bail to suppress runaway recursion. michael@0: */ michael@0: AutoResolving resolving(cx, obj, id); michael@0: if (resolving.alreadyStarted()) { michael@0: /* Already resolving id in obj -- suppress recursion. */ michael@0: *recursedp = true; michael@0: return true; michael@0: } michael@0: *recursedp = false; michael@0: michael@0: propp.set(nullptr); michael@0: michael@0: if (clasp->flags & JSCLASS_NEW_RESOLVE) { michael@0: JSNewResolveOp newresolve = reinterpret_cast(resolve); michael@0: RootedObject obj2(cx, nullptr); michael@0: if (!newresolve(cx, obj, id, &obj2)) michael@0: return false; michael@0: michael@0: /* michael@0: * We trust the new style resolve hook to set obj2 to nullptr when michael@0: * the id cannot be resolved. But, when obj2 is not null, we do michael@0: * not assume that id must exist and do full nativeLookup for michael@0: * compatibility. michael@0: */ michael@0: if (!obj2) michael@0: return true; michael@0: michael@0: if (!obj2->isNative()) { michael@0: /* Whoops, newresolve handed back a foreign obj2. */ michael@0: JS_ASSERT(obj2 != obj); michael@0: return JSObject::lookupGeneric(cx, obj2, id, objp, propp); michael@0: } michael@0: michael@0: objp.set(obj2); michael@0: } else { michael@0: if (!resolve(cx, obj, id)) michael@0: return false; michael@0: michael@0: objp.set(obj); michael@0: } michael@0: michael@0: if (JSID_IS_INT(id) && objp->containsDenseElement(JSID_TO_INT(id))) { michael@0: MarkDenseOrTypedArrayElementFound(propp); michael@0: return true; michael@0: } michael@0: michael@0: Shape *shape; michael@0: if (!objp->nativeEmpty() && (shape = objp->nativeLookup(cx, id))) michael@0: propp.set(shape); michael@0: else michael@0: objp.set(nullptr); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static MOZ_ALWAYS_INLINE bool michael@0: LookupOwnPropertyInline(ExclusiveContext *cx, michael@0: typename MaybeRooted::HandleType obj, michael@0: typename MaybeRooted::HandleType id, michael@0: typename MaybeRooted::MutableHandleType objp, michael@0: typename MaybeRooted::MutableHandleType propp, michael@0: bool *donep) michael@0: { michael@0: // Check for a native dense element. michael@0: if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { michael@0: objp.set(obj); michael@0: MarkDenseOrTypedArrayElementFound(propp); michael@0: *donep = true; michael@0: return true; michael@0: } michael@0: michael@0: // Check for a typed array element. Integer lookups always finish here michael@0: // so that integer properties on the prototype are ignored even for out michael@0: // of bounds accesses. michael@0: if (obj->template is()) { michael@0: uint64_t index; michael@0: if (IsTypedArrayIndex(id, &index)) { michael@0: if (index < obj->template as().length()) { michael@0: objp.set(obj); michael@0: MarkDenseOrTypedArrayElementFound(propp); michael@0: } else { michael@0: objp.set(nullptr); michael@0: propp.set(nullptr); michael@0: } michael@0: *donep = true; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Check for a native property. michael@0: if (Shape *shape = obj->nativeLookup(cx, id)) { michael@0: objp.set(obj); michael@0: propp.set(shape); michael@0: *donep = true; michael@0: return true; michael@0: } michael@0: michael@0: // id was not found in obj. Try obj's resolve hook, if any. michael@0: if (obj->getClass()->resolve != JS_ResolveStub) { michael@0: if (!cx->shouldBeJSContext() || !allowGC) michael@0: return false; michael@0: michael@0: bool recursed; michael@0: if (!CallResolveOp(cx->asJSContext(), michael@0: MaybeRooted::toHandle(obj), michael@0: MaybeRooted::toHandle(id), michael@0: MaybeRooted::toMutableHandle(objp), michael@0: MaybeRooted::toMutableHandle(propp), michael@0: &recursed)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (recursed) { michael@0: objp.set(nullptr); michael@0: propp.set(nullptr); michael@0: *donep = true; michael@0: return true; michael@0: } michael@0: michael@0: if (propp) { michael@0: *donep = true; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: *donep = false; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NativeLookupOwnProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandle shapep) michael@0: { michael@0: RootedObject pobj(cx); michael@0: bool done; michael@0: michael@0: if (!LookupOwnPropertyInline(cx, obj, id, &pobj, shapep, &done)) michael@0: return false; michael@0: if (!done || pobj != obj) michael@0: shapep.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static MOZ_ALWAYS_INLINE bool michael@0: LookupPropertyInline(ExclusiveContext *cx, michael@0: typename MaybeRooted::HandleType obj, michael@0: typename MaybeRooted::HandleType id, michael@0: typename MaybeRooted::MutableHandleType objp, michael@0: typename MaybeRooted::MutableHandleType propp) michael@0: { michael@0: /* NB: The logic of this procedure is implicitly reflected in IonBuilder.cpp's michael@0: * |CanEffectlesslyCallLookupGenericOnObject| logic. michael@0: * If this changes, please remember to update the logic there as well. michael@0: */ michael@0: michael@0: /* Search scopes starting with obj and following the prototype link. */ michael@0: typename MaybeRooted::RootType current(cx, obj); michael@0: michael@0: while (true) { michael@0: bool done; michael@0: if (!LookupOwnPropertyInline(cx, current, id, objp, propp, &done)) michael@0: return false; michael@0: if (done) michael@0: return true; michael@0: michael@0: typename MaybeRooted::RootType proto(cx, current->getProto()); michael@0: michael@0: if (!proto) michael@0: break; michael@0: if (!proto->isNative()) { michael@0: if (!cx->shouldBeJSContext() || !allowGC) michael@0: return false; michael@0: return JSObject::lookupGeneric(cx->asJSContext(), michael@0: MaybeRooted::toHandle(proto), michael@0: MaybeRooted::toHandle(id), michael@0: MaybeRooted::toMutableHandle(objp), michael@0: MaybeRooted::toMutableHandle(propp)); michael@0: } michael@0: michael@0: current = proto; michael@0: } michael@0: michael@0: objp.set(nullptr); michael@0: propp.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: baseops::LookupProperty(ExclusiveContext *cx, michael@0: typename MaybeRooted::HandleType obj, michael@0: typename MaybeRooted::HandleType id, michael@0: typename MaybeRooted::MutableHandleType objp, michael@0: typename MaybeRooted::MutableHandleType propp) michael@0: { michael@0: return LookupPropertyInline(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: template bool michael@0: baseops::LookupProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandleObject objp, MutableHandleShape propp); michael@0: michael@0: template bool michael@0: baseops::LookupProperty(ExclusiveContext *cx, JSObject *obj, jsid id, michael@0: FakeMutableHandle objp, michael@0: FakeMutableHandle propp); michael@0: michael@0: /* static */ bool michael@0: JSObject::lookupGeneric(JSContext *cx, HandleObject obj, js::HandleId id, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: /* michael@0: * NB: The logic of lookupGeneric is implicitly reflected in IonBuilder.cpp's michael@0: * |CanEffectlesslyCallLookupGenericOnObject| logic. michael@0: * If this changes, please remember to update the logic there as well. michael@0: */ michael@0: LookupGenericOp op = obj->getOps()->lookupGeneric; michael@0: if (op) michael@0: return op(cx, obj, id, objp, propp); michael@0: return baseops::LookupProperty(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: bool michael@0: baseops::LookupElement(JSContext *cx, HandleObject obj, uint32_t index, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: michael@0: return LookupPropertyInline(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: bool michael@0: js::LookupNativeProperty(ExclusiveContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: return LookupPropertyInline(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: bool michael@0: js::LookupName(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, michael@0: MutableHandleObject objp, MutableHandleObject pobjp, MutableHandleShape propp) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: michael@0: for (RootedObject scope(cx, scopeChain); scope; scope = scope->enclosingScope()) { michael@0: if (!JSObject::lookupGeneric(cx, scope, id, pobjp, propp)) michael@0: return false; michael@0: if (propp) { michael@0: objp.set(scope); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: objp.set(nullptr); michael@0: pobjp.set(nullptr); michael@0: propp.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::LookupNameNoGC(JSContext *cx, PropertyName *name, JSObject *scopeChain, michael@0: JSObject **objp, JSObject **pobjp, Shape **propp) michael@0: { michael@0: AutoAssertNoException nogc(cx); michael@0: michael@0: JS_ASSERT(!*objp && !*pobjp && !*propp); michael@0: michael@0: for (JSObject *scope = scopeChain; scope; scope = scope->enclosingScope()) { michael@0: if (scope->getOps()->lookupGeneric) michael@0: return false; michael@0: if (!LookupPropertyInline(cx, scope, NameToId(name), pobjp, propp)) michael@0: return false; michael@0: if (*propp) { michael@0: *objp = scope; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, michael@0: MutableHandleObject objp) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: michael@0: RootedObject pobj(cx); michael@0: RootedShape prop(cx); michael@0: michael@0: RootedObject scope(cx, scopeChain); michael@0: for (; !scope->is(); scope = scope->enclosingScope()) { michael@0: if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop)) michael@0: return false; michael@0: if (prop) michael@0: break; michael@0: } michael@0: michael@0: objp.set(scope); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup, michael@0: typename MaybeRooted::HandleType obj, michael@0: typename MaybeRooted::HandleType id, michael@0: typename MaybeRooted::MutableHandleType objp, michael@0: typename MaybeRooted::MutableHandleType propp) michael@0: { michael@0: if (lookup) { michael@0: if (!allowGC) michael@0: return false; michael@0: if (!lookup(cx, michael@0: MaybeRooted::toHandle(obj), michael@0: MaybeRooted::toHandle(id), michael@0: MaybeRooted::toMutableHandle(objp), michael@0: MaybeRooted::toMutableHandle(propp))) michael@0: { michael@0: return false; michael@0: } michael@0: } else { michael@0: bool done; michael@0: if (!LookupOwnPropertyInline(cx, obj, id, objp, propp, &done)) michael@0: return false; michael@0: if (!done) { michael@0: objp.set(nullptr); michael@0: propp.set(nullptr); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (!propp) michael@0: return true; michael@0: michael@0: if (objp == obj) michael@0: return true; michael@0: michael@0: JSObject *outer = nullptr; michael@0: if (JSObjectOp op = objp->getClass()->ext.outerObject) { michael@0: if (!allowGC) michael@0: return false; michael@0: RootedObject inner(cx, objp); michael@0: outer = op(cx, inner); michael@0: if (!outer) michael@0: return false; michael@0: } michael@0: michael@0: if (outer != objp) michael@0: propp.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup, michael@0: HandleObject obj, HandleId id, michael@0: MutableHandleObject objp, MutableHandleShape propp); michael@0: michael@0: template bool michael@0: js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup, michael@0: JSObject *obj, jsid id, michael@0: FakeMutableHandle objp, FakeMutableHandle propp); michael@0: michael@0: bool michael@0: js::HasOwnProperty(JSContext *cx, HandleObject obj, HandleId id, bool *resultp) michael@0: { michael@0: RootedObject pobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!HasOwnProperty(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) michael@0: return false; michael@0: *resultp = (shape != nullptr); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static MOZ_ALWAYS_INLINE bool michael@0: NativeGetInline(JSContext *cx, michael@0: typename MaybeRooted::HandleType obj, michael@0: typename MaybeRooted::HandleType receiver, michael@0: typename MaybeRooted::HandleType pobj, michael@0: typename MaybeRooted::HandleType shape, michael@0: typename MaybeRooted::MutableHandleType vp) michael@0: { michael@0: JS_ASSERT(pobj->isNative()); michael@0: michael@0: if (shape->hasSlot()) { michael@0: vp.set(pobj->nativeGetSlot(shape->slot())); michael@0: JS_ASSERT(!vp.isMagic()); michael@0: JS_ASSERT_IF(!pobj->hasSingletonType() && michael@0: !pobj->template is() && michael@0: shape->hasDefaultGetter(), michael@0: js::types::TypeHasProperty(cx, pobj->type(), shape->propid(), vp)); michael@0: } else { michael@0: vp.setUndefined(); michael@0: } michael@0: if (shape->hasDefaultGetter()) michael@0: return true; michael@0: michael@0: { michael@0: jsbytecode *pc; michael@0: JSScript *script = cx->currentScript(&pc); michael@0: #ifdef JS_ION michael@0: if (script && script->hasBaselineScript()) { michael@0: switch (JSOp(*pc)) { michael@0: case JSOP_GETPROP: michael@0: case JSOP_CALLPROP: michael@0: case JSOP_LENGTH: michael@0: script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc)); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: if (!allowGC) michael@0: return false; michael@0: michael@0: if (!shape->get(cx, michael@0: MaybeRooted::toHandle(receiver), michael@0: MaybeRooted::toHandle(obj), michael@0: MaybeRooted::toHandle(pobj), michael@0: MaybeRooted::toMutableHandle(vp))) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: /* Update slotful shapes according to the value produced by the getter. */ michael@0: if (shape->hasSlot() && pobj->nativeContains(cx, shape)) michael@0: pobj->nativeSetSlot(shape->slot(), vp); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::NativeGet(JSContext *cx, Handle obj, Handle pobj, Handle shape, michael@0: MutableHandle vp) michael@0: { michael@0: return NativeGetInline(cx, obj, obj, pobj, shape, vp); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::NativeSet(typename ExecutionModeTraits::ContextType cxArg, michael@0: Handle obj, Handle receiver, michael@0: HandleShape shape, bool strict, MutableHandleValue vp) michael@0: { michael@0: JS_ASSERT(cxArg->isThreadLocal(obj)); michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: if (shape->hasSlot()) { michael@0: /* If shape has a stub setter, just store vp. */ michael@0: if (shape->hasDefaultSetter()) { michael@0: if (mode == ParallelExecution) { michael@0: if (!obj->nativeSetSlotIfHasType(shape, vp)) michael@0: return false; michael@0: } else { michael@0: obj->nativeSetSlotWithType(cxArg->asExclusiveContext(), shape, vp); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: michael@0: if (!shape->hasSlot()) { michael@0: /* michael@0: * Allow API consumers to create shared properties with stub setters. michael@0: * Such properties effectively function as data descriptors which are michael@0: * not writable, so attempting to set such a property should do nothing michael@0: * or throw if we're in strict mode. michael@0: */ michael@0: if (!shape->hasGetterValue() && shape->hasDefaultSetter()) michael@0: return js_ReportGetterOnlyAssignment(cx, strict); michael@0: } michael@0: michael@0: RootedValue ovp(cx, vp); michael@0: michael@0: uint32_t sample = cx->runtime()->propertyRemovals; michael@0: if (!shape->set(cx, obj, receiver, strict, vp)) michael@0: return false; michael@0: michael@0: /* michael@0: * Update any slot for the shape with the value produced by the setter, michael@0: * unless the setter deleted the shape. michael@0: */ michael@0: if (shape->hasSlot() && michael@0: (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) || michael@0: obj->nativeContains(cx, shape))) michael@0: { michael@0: obj->setSlot(shape->slot(), vp); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::NativeSet(JSContext *cx, michael@0: Handle obj, Handle receiver, michael@0: HandleShape shape, bool strict, MutableHandleValue vp); michael@0: template bool michael@0: js::NativeSet(ForkJoinContext *cx, michael@0: Handle obj, Handle receiver, michael@0: HandleShape shape, bool strict, MutableHandleValue vp); michael@0: michael@0: template michael@0: static MOZ_ALWAYS_INLINE bool michael@0: GetPropertyHelperInline(JSContext *cx, michael@0: typename MaybeRooted::HandleType obj, michael@0: typename MaybeRooted::HandleType receiver, michael@0: typename MaybeRooted::HandleType id, michael@0: typename MaybeRooted::MutableHandleType vp) michael@0: { michael@0: /* This call site is hot -- use the always-inlined variant of LookupNativeProperty(). */ michael@0: typename MaybeRooted::RootType obj2(cx); michael@0: typename MaybeRooted::RootType shape(cx); michael@0: if (!LookupPropertyInline(cx, obj, id, &obj2, &shape)) michael@0: return false; michael@0: michael@0: if (!shape) { michael@0: if (!allowGC) michael@0: return false; michael@0: michael@0: vp.setUndefined(); michael@0: michael@0: if (!CallJSPropertyOp(cx, obj->getClass()->getProperty, michael@0: MaybeRooted::toHandle(obj), michael@0: MaybeRooted::toHandle(id), michael@0: MaybeRooted::toMutableHandle(vp))) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Give a strict warning if foo.bar is evaluated by a script for an michael@0: * object foo with no property named 'bar'. michael@0: */ michael@0: if (vp.isUndefined()) { michael@0: jsbytecode *pc = nullptr; michael@0: RootedScript script(cx, cx->currentScript(&pc)); michael@0: if (!pc) michael@0: return true; michael@0: JSOp op = (JSOp) *pc; michael@0: michael@0: if (op == JSOP_GETXPROP) { michael@0: /* Undefined property during a name lookup, report an error. */ michael@0: JSAutoByteString printable; michael@0: if (js_ValueToPrintable(cx, IdToValue(id), &printable)) michael@0: js_ReportIsNotDefined(cx, printable.ptr()); michael@0: return false; michael@0: } michael@0: michael@0: /* Don't warn if extra warnings not enabled or for random getprop operations. */ michael@0: if (!cx->options().extraWarnings() || (op != JSOP_GETPROP && op != JSOP_GETELEM)) michael@0: return true; michael@0: michael@0: /* Don't warn repeatedly for the same script. */ michael@0: if (!script || script->warnedAboutUndefinedProp()) michael@0: return true; michael@0: michael@0: /* michael@0: * Don't warn in self-hosted code (where the further presence of michael@0: * JS::ContextOptions::werror() would result in impossible-to-avoid michael@0: * errors to entirely-innocent client code). michael@0: */ michael@0: if (script->selfHosted()) michael@0: return true; michael@0: michael@0: /* We may just be checking if that object has an iterator. */ michael@0: if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic)) michael@0: return true; michael@0: michael@0: /* Do not warn about tests like (obj[prop] == undefined). */ michael@0: pc += js_CodeSpec[op].length; michael@0: if (Detecting(cx, script, pc)) michael@0: return true; michael@0: michael@0: unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT; michael@0: script->setWarnedAboutUndefinedProp(); michael@0: michael@0: /* Ok, bad undefined property reference: whine about it. */ michael@0: RootedValue val(cx, IdToValue(id)); michael@0: if (!js_ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP, michael@0: JSDVG_IGNORE_STACK, val, js::NullPtr(), michael@0: nullptr, nullptr)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: if (!obj2->isNative()) { michael@0: if (!allowGC) michael@0: return false; michael@0: HandleObject obj2Handle = MaybeRooted::toHandle(obj2); michael@0: HandleObject receiverHandle = MaybeRooted::toHandle(receiver); michael@0: HandleId idHandle = MaybeRooted::toHandle(id); michael@0: MutableHandleValue vpHandle = MaybeRooted::toMutableHandle(vp); michael@0: return obj2->template is() michael@0: ? Proxy::get(cx, obj2Handle, receiverHandle, idHandle, vpHandle) michael@0: : JSObject::getGeneric(cx, obj2Handle, obj2Handle, idHandle, vpHandle); michael@0: } michael@0: michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: vp.set(obj2->getDenseOrTypedArrayElement(JSID_TO_INT(id))); michael@0: return true; michael@0: } michael@0: michael@0: /* This call site is hot -- use the always-inlined variant of NativeGet(). */ michael@0: if (!NativeGetInline(cx, obj, receiver, obj2, shape, vp)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: baseops::GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, MutableHandleValue vp) michael@0: { michael@0: /* This call site is hot -- use the always-inlined variant of GetPropertyHelper(). */ michael@0: return GetPropertyHelperInline(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: bool michael@0: baseops::GetPropertyNoGC(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Value *vp) michael@0: { michael@0: AutoAssertNoException nogc(cx); michael@0: return GetPropertyHelperInline(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: LookupPropertyPureInline(JSObject *obj, jsid id, JSObject **objp, Shape **propp) michael@0: { michael@0: if (!obj->isNative()) michael@0: return false; michael@0: michael@0: JSObject *current = obj; michael@0: while (true) { michael@0: /* Search for a native dense element, typed array element, or property. */ michael@0: michael@0: if (JSID_IS_INT(id) && current->containsDenseElement(JSID_TO_INT(id))) { michael@0: *objp = current; michael@0: MarkDenseOrTypedArrayElementFound(propp); michael@0: return true; michael@0: } michael@0: michael@0: if (current->is()) { michael@0: uint64_t index; michael@0: if (IsTypedArrayIndex(id, &index)) { michael@0: if (index < obj->as().length()) { michael@0: *objp = current; michael@0: MarkDenseOrTypedArrayElementFound(propp); michael@0: } else { michael@0: *objp = nullptr; michael@0: *propp = nullptr; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (Shape *shape = current->nativeLookupPure(id)) { michael@0: *objp = current; michael@0: *propp = shape; michael@0: return true; michael@0: } michael@0: michael@0: /* Fail if there's a resolve hook. */ michael@0: if (current->getClass()->resolve != JS_ResolveStub) michael@0: return false; michael@0: michael@0: JSObject *proto = current->getProto(); michael@0: michael@0: if (!proto) michael@0: break; michael@0: if (!proto->isNative()) michael@0: return false; michael@0: michael@0: current = proto; michael@0: } michael@0: michael@0: *objp = nullptr; michael@0: *propp = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: NativeGetPureInline(JSObject *pobj, Shape *shape, Value *vp) michael@0: { michael@0: JS_ASSERT(pobj->isNative()); michael@0: michael@0: if (shape->hasSlot()) { michael@0: *vp = pobj->nativeGetSlot(shape->slot()); michael@0: JS_ASSERT(!vp->isMagic()); michael@0: } else { michael@0: vp->setUndefined(); michael@0: } michael@0: michael@0: /* Fail if we have a custom getter. */ michael@0: return shape->hasDefaultGetter(); michael@0: } michael@0: michael@0: bool michael@0: js::LookupPropertyPure(JSObject *obj, jsid id, JSObject **objp, Shape **propp) michael@0: { michael@0: return LookupPropertyPureInline(obj, id, objp, propp); michael@0: } michael@0: michael@0: static inline bool michael@0: IdIsLength(ThreadSafeContext *cx, jsid id) michael@0: { michael@0: return JSID_IS_ATOM(id) && cx->names().length == JSID_TO_ATOM(id); michael@0: } michael@0: michael@0: /* michael@0: * A pure version of GetPropertyHelper that can be called from parallel code michael@0: * without locking. This code path cannot GC. This variant returns false michael@0: * whenever a side-effect might have occured in the effectful version. This michael@0: * includes, but is not limited to: michael@0: * michael@0: * - Any object in the lookup chain has a non-stub resolve hook. michael@0: * - Any object in the lookup chain is non-native. michael@0: * - The property has a getter. michael@0: */ michael@0: bool michael@0: js::GetPropertyPure(ThreadSafeContext *cx, JSObject *obj, jsid id, Value *vp) michael@0: { michael@0: /* Deal with native objects. */ michael@0: JSObject *obj2; michael@0: Shape *shape; michael@0: if (!LookupPropertyPureInline(obj, id, &obj2, &shape)) michael@0: return false; michael@0: michael@0: if (!shape) { michael@0: /* Fail if we have a non-stub class op hooks. */ michael@0: if (obj->getClass()->getProperty && obj->getClass()->getProperty != JS_PropertyStub) michael@0: return false; michael@0: michael@0: if (obj->getOps()->getElement) michael@0: return false; michael@0: michael@0: /* Vanilla native object, return undefined. */ michael@0: vp->setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: *vp = obj2->getDenseOrTypedArrayElement(JSID_TO_INT(id)); michael@0: return true; michael@0: } michael@0: michael@0: /* Special case 'length' on Array and TypedArray. */ michael@0: if (IdIsLength(cx, id)) { michael@0: if (obj->is()) { michael@0: vp->setNumber(obj->as().length()); michael@0: return true; michael@0: } michael@0: michael@0: if (obj->is()) { michael@0: vp->setNumber(obj->as().length()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return NativeGetPureInline(obj2, shape, vp); michael@0: } michael@0: michael@0: static bool michael@0: MOZ_ALWAYS_INLINE michael@0: GetElementPure(ThreadSafeContext *cx, JSObject *obj, uint32_t index, Value *vp) michael@0: { michael@0: if (index <= JSID_INT_MAX) michael@0: return GetPropertyPure(cx, obj, INT_TO_JSID(index), vp); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * A pure version of GetObjectElementOperation that can be called from michael@0: * parallel code without locking. This variant returns false whenever a michael@0: * side-effect might have occurred. michael@0: */ michael@0: bool michael@0: js::GetObjectElementOperationPure(ThreadSafeContext *cx, JSObject *obj, const Value &prop, michael@0: Value *vp) michael@0: { michael@0: uint32_t index; michael@0: if (IsDefinitelyIndex(prop, &index)) michael@0: return GetElementPure(cx, obj, index, vp); michael@0: michael@0: /* Atomizing the property value is effectful and not threadsafe. */ michael@0: if (!prop.isString() || !prop.toString()->isAtom()) michael@0: return false; michael@0: michael@0: JSAtom *name = &prop.toString()->asAtom(); michael@0: if (name->isIndex(&index)) michael@0: return GetElementPure(cx, obj, index, vp); michael@0: michael@0: return GetPropertyPure(cx, obj, NameToId(name->asPropertyName()), vp); michael@0: } michael@0: michael@0: bool michael@0: baseops::GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: michael@0: /* This call site is hot -- use the always-inlined variant of js_GetPropertyHelper(). */ michael@0: return GetPropertyHelperInline(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: static bool michael@0: MaybeReportUndeclaredVarAssignment(JSContext *cx, JSString *propname) michael@0: { michael@0: { michael@0: JSScript *script = cx->currentScript(nullptr, JSContext::ALLOW_CROSS_COMPARTMENT); michael@0: if (!script) michael@0: return true; michael@0: michael@0: // If the code is not strict and extra warnings aren't enabled, then no michael@0: // check is needed. michael@0: if (!script->strict() && !cx->options().extraWarnings()) michael@0: return true; michael@0: } michael@0: michael@0: JSAutoByteString bytes(cx, propname); michael@0: return !!bytes && michael@0: JS_ReportErrorFlagsAndNumber(cx, michael@0: (JSREPORT_WARNING | JSREPORT_STRICT michael@0: | JSREPORT_STRICT_MODE_ERROR), michael@0: js_GetErrorMessage, nullptr, michael@0: JSMSG_UNDECLARED_VAR, bytes.ptr()); michael@0: } michael@0: michael@0: bool michael@0: js::ReportIfUndeclaredVarAssignment(JSContext *cx, HandleString propname) michael@0: { michael@0: { michael@0: jsbytecode *pc; michael@0: JSScript *script = cx->currentScript(&pc, JSContext::ALLOW_CROSS_COMPARTMENT); michael@0: if (!script) michael@0: return true; michael@0: michael@0: // If the code is not strict and extra warnings aren't enabled, then no michael@0: // check is needed. michael@0: if (!script->strict() && !cx->options().extraWarnings()) michael@0: return true; michael@0: michael@0: /* michael@0: * We only need to check for bare name mutations: we shouldn't be michael@0: * warning, or throwing, or whatever, if we're not doing a variable michael@0: * access. michael@0: * michael@0: * TryConvertToGname in frontend/BytecodeEmitter.cpp checks for rather michael@0: * more opcodes when it does, in the normal course of events, what this michael@0: * method does in the abnormal course of events. Because we're called michael@0: * in narrower circumstances, we only need check two. We don't need to michael@0: * check for the increment/decrement opcodes because they're no-ops: michael@0: * the actual semantics are implemented by desugaring. And we don't michael@0: * need to check name-access because this method is only supposed to be michael@0: * called in assignment contexts. michael@0: */ michael@0: MOZ_ASSERT(*pc != JSOP_NAME); michael@0: MOZ_ASSERT(*pc != JSOP_GETGNAME); michael@0: if (*pc != JSOP_SETNAME && *pc != JSOP_SETGNAME) michael@0: return true; michael@0: } michael@0: michael@0: JSAutoByteString bytes(cx, propname); michael@0: return !!bytes && michael@0: JS_ReportErrorFlagsAndNumber(cx, michael@0: JSREPORT_WARNING | JSREPORT_STRICT | michael@0: JSREPORT_STRICT_MODE_ERROR, michael@0: js_GetErrorMessage, nullptr, michael@0: JSMSG_UNDECLARED_VAR, bytes.ptr()); michael@0: } michael@0: michael@0: bool michael@0: JSObject::reportReadOnly(ThreadSafeContext *cxArg, jsid id, unsigned report) michael@0: { michael@0: if (cxArg->isForkJoinContext()) michael@0: return cxArg->asForkJoinContext()->reportError(ParallelBailoutUnsupportedVM, report); michael@0: michael@0: if (!cxArg->isJSContext()) michael@0: return true; michael@0: michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: RootedValue val(cx, IdToValue(id)); michael@0: return js_ReportValueErrorFlags(cx, report, JSMSG_READ_ONLY, michael@0: JSDVG_IGNORE_STACK, val, js::NullPtr(), michael@0: nullptr, nullptr); michael@0: } michael@0: michael@0: bool michael@0: JSObject::reportNotConfigurable(ThreadSafeContext *cxArg, jsid id, unsigned report) michael@0: { michael@0: if (cxArg->isForkJoinContext()) michael@0: return cxArg->asForkJoinContext()->reportError(ParallelBailoutUnsupportedVM, report); michael@0: michael@0: if (!cxArg->isJSContext()) michael@0: return true; michael@0: michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: RootedValue val(cx, IdToValue(id)); michael@0: return js_ReportValueErrorFlags(cx, report, JSMSG_CANT_DELETE, michael@0: JSDVG_IGNORE_STACK, val, js::NullPtr(), michael@0: nullptr, nullptr); michael@0: } michael@0: michael@0: bool michael@0: JSObject::reportNotExtensible(ThreadSafeContext *cxArg, unsigned report) michael@0: { michael@0: if (cxArg->isForkJoinContext()) michael@0: return cxArg->asForkJoinContext()->reportError(ParallelBailoutUnsupportedVM, report); michael@0: michael@0: if (!cxArg->isJSContext()) michael@0: return true; michael@0: michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: RootedValue val(cx, ObjectValue(*this)); michael@0: return js_ReportValueErrorFlags(cx, report, JSMSG_OBJECT_NOT_EXTENSIBLE, michael@0: JSDVG_IGNORE_STACK, val, js::NullPtr(), michael@0: nullptr, nullptr); michael@0: } michael@0: michael@0: bool michael@0: JSObject::callMethod(JSContext *cx, HandleId id, unsigned argc, Value *argv, MutableHandleValue vp) michael@0: { michael@0: RootedValue fval(cx); michael@0: RootedObject obj(cx, this); michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, &fval)) michael@0: return false; michael@0: return Invoke(cx, ObjectValue(*obj), fval, argc, argv, vp); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: baseops::SetPropertyHelper(typename ExecutionModeTraits::ContextType cxArg, michael@0: HandleObject obj, HandleObject receiver, HandleId id, michael@0: QualifiedBool qualified, MutableHandleValue vp, bool strict) michael@0: { michael@0: JS_ASSERT(cxArg->isThreadLocal(obj)); michael@0: michael@0: if (MOZ_UNLIKELY(obj->watched())) { michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: /* Fire watchpoints, if any. */ michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: WatchpointMap *wpmap = cx->compartment()->watchpointMap; michael@0: if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) michael@0: return false; michael@0: } michael@0: michael@0: RootedObject pobj(cxArg); michael@0: RootedShape shape(cxArg); michael@0: if (mode == ParallelExecution) { michael@0: if (!LookupPropertyPure(obj, id, pobj.address(), shape.address())) michael@0: return false; michael@0: } else { michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: if (!LookupNativeProperty(cx, obj, id, &pobj, &shape)) michael@0: return false; michael@0: } michael@0: if (shape) { michael@0: if (!pobj->isNative()) { michael@0: if (pobj->is()) { michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: Rooted pd(cx); michael@0: if (!Proxy::getPropertyDescriptor(cx, pobj, id, &pd)) michael@0: return false; michael@0: michael@0: if ((pd.attributes() & (JSPROP_SHARED | JSPROP_SHADOWABLE)) == JSPROP_SHARED) { michael@0: return !pd.setter() || michael@0: CallSetter(cx, receiver, id, pd.setter(), pd.attributes(), strict, vp); michael@0: } michael@0: michael@0: if (pd.isReadonly()) { michael@0: if (strict) michael@0: return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); michael@0: if (cx->options().extraWarnings()) michael@0: return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: shape = nullptr; michael@0: } michael@0: } else { michael@0: /* We should never add properties to lexical blocks. */ michael@0: JS_ASSERT(!obj->is()); michael@0: michael@0: if (obj->is() && !qualified) { michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: if (!MaybeReportUndeclaredVarAssignment(cxArg->asJSContext(), JSID_TO_STRING(id))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Now either shape is null, meaning id was not found in obj or one of its michael@0: * prototypes; or shape is non-null, meaning id was found directly in pobj. michael@0: */ michael@0: unsigned attrs = JSPROP_ENUMERATE; michael@0: const Class *clasp = obj->getClass(); michael@0: PropertyOp getter = clasp->getProperty; michael@0: StrictPropertyOp setter = clasp->setProperty; michael@0: michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: /* ES5 8.12.4 [[Put]] step 2, for a dense data property on pobj. */ michael@0: if (pobj != obj) michael@0: shape = nullptr; michael@0: } else if (shape) { michael@0: /* ES5 8.12.4 [[Put]] step 2. */ michael@0: if (shape->isAccessorDescriptor()) { michael@0: if (shape->hasDefaultSetter()) { michael@0: /* Bail out of parallel execution if we are strict to throw. */ michael@0: if (mode == ParallelExecution) michael@0: return !strict; michael@0: michael@0: return js_ReportGetterOnlyAssignment(cxArg->asJSContext(), strict); michael@0: } michael@0: } else { michael@0: JS_ASSERT(shape->isDataDescriptor()); michael@0: michael@0: if (!shape->writable()) { michael@0: /* michael@0: * Error in strict mode code, warn with extra warnings michael@0: * options, otherwise do nothing. michael@0: * michael@0: * Bail out of parallel execution if we are strict to throw. michael@0: */ michael@0: if (mode == ParallelExecution) michael@0: return !strict; michael@0: michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: if (strict) michael@0: return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); michael@0: if (cx->options().extraWarnings()) michael@0: return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: attrs = shape->attributes(); michael@0: if (pobj != obj) { michael@0: /* michael@0: * We found id in a prototype object: prepare to share or shadow. michael@0: */ michael@0: if (!shape->shadowable()) { michael@0: if (shape->hasDefaultSetter() && !shape->hasGetterValue()) michael@0: return true; michael@0: michael@0: if (mode == ParallelExecution) michael@0: return false; michael@0: michael@0: return shape->set(cxArg->asJSContext(), obj, receiver, strict, vp); michael@0: } michael@0: michael@0: /* michael@0: * Preserve attrs except JSPROP_SHARED, getter, and setter when michael@0: * shadowing any property that has no slot (is shared). We must michael@0: * clear the shared attribute for the shadowing shape so that the michael@0: * property in obj that it defines has a slot to retain the value michael@0: * being set, in case the setter simply cannot operate on instances michael@0: * of obj's class by storing the value in some class-specific michael@0: * location. michael@0: */ michael@0: if (!shape->hasSlot()) { michael@0: attrs &= ~JSPROP_SHARED; michael@0: getter = shape->getter(); michael@0: setter = shape->setter(); michael@0: } else { michael@0: /* Restore attrs to the ECMA default for new properties. */ michael@0: attrs = JSPROP_ENUMERATE; michael@0: } michael@0: michael@0: /* michael@0: * Forget we found the proto-property now that we've copied any michael@0: * needed member values. michael@0: */ michael@0: shape = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: uint32_t index = JSID_TO_INT(id); michael@0: michael@0: if (obj->is()) { michael@0: double d; michael@0: if (mode == ParallelExecution) { michael@0: // Bail if converting the value might invoke user-defined michael@0: // conversions. michael@0: if (vp.isObject()) michael@0: return false; michael@0: if (!NonObjectToNumber(cxArg, vp, &d)) michael@0: return false; michael@0: } else { michael@0: if (!ToNumber(cxArg->asJSContext(), vp, &d)) michael@0: return false; michael@0: } michael@0: michael@0: // Silently do nothing for out-of-bounds sets, for consistency with michael@0: // current behavior. (ES6 currently says to throw for this in michael@0: // strict mode code, so we may eventually need to change.) michael@0: TypedArrayObject &tarray = obj->as(); michael@0: if (index < tarray.length()) michael@0: TypedArrayObject::setElement(tarray, index, d); michael@0: return true; michael@0: } michael@0: michael@0: bool definesPast; michael@0: if (!WouldDefinePastNonwritableLength(cxArg, obj, index, strict, &definesPast)) michael@0: return false; michael@0: if (definesPast) { michael@0: /* Bail out of parallel execution if we are strict to throw. */ michael@0: if (mode == ParallelExecution) michael@0: return !strict; michael@0: return true; michael@0: } michael@0: michael@0: if (mode == ParallelExecution) michael@0: return obj->setDenseElementIfHasType(index, vp); michael@0: michael@0: obj->setDenseElementWithType(cxArg->asJSContext(), index, vp); michael@0: return true; michael@0: } michael@0: michael@0: if (obj->is() && id == NameToId(cxArg->names().length)) { michael@0: Rooted arr(cxArg, &obj->as()); michael@0: return ArraySetLength(cxArg, arr, id, attrs, vp, strict); michael@0: } michael@0: michael@0: if (!shape) { michael@0: bool extensible; michael@0: if (mode == ParallelExecution) { michael@0: if (obj->is()) michael@0: return false; michael@0: extensible = obj->nonProxyIsExtensible(); michael@0: } else { michael@0: if (!JSObject::isExtensible(cxArg->asJSContext(), obj, &extensible)) michael@0: return false; michael@0: } michael@0: michael@0: if (!extensible) { michael@0: /* Error in strict mode code, warn with extra warnings option, otherwise do nothing. */ michael@0: if (strict) michael@0: return obj->reportNotExtensible(cxArg); michael@0: if (mode == SequentialExecution && cxArg->asJSContext()->options().extraWarnings()) michael@0: return obj->reportNotExtensible(cxArg, JSREPORT_STRICT | JSREPORT_WARNING); michael@0: return true; michael@0: } michael@0: michael@0: if (mode == ParallelExecution) { michael@0: if (obj->isDelegate()) michael@0: return false; michael@0: michael@0: if (getter != JS_PropertyStub || !HasTypePropertyId(obj, id, vp)) michael@0: return false; michael@0: } else { michael@0: JSContext *cx = cxArg->asJSContext(); michael@0: michael@0: /* Purge the property cache of now-shadowed id in obj's scope chain. */ michael@0: if (!PurgeScopeChain(cx, obj, id)) michael@0: return false; michael@0: } michael@0: michael@0: return DefinePropertyOrElement(cxArg, obj, id, getter, setter, michael@0: attrs, vp, true, strict); michael@0: } michael@0: michael@0: return NativeSet(cxArg, obj, receiver, shape, strict, vp); michael@0: } michael@0: michael@0: template bool michael@0: baseops::SetPropertyHelper(JSContext *cx, HandleObject obj, michael@0: HandleObject receiver, HandleId id, michael@0: QualifiedBool qualified, michael@0: MutableHandleValue vp, bool strict); michael@0: template bool michael@0: baseops::SetPropertyHelper(ForkJoinContext *cx, HandleObject obj, michael@0: HandleObject receiver, HandleId id, michael@0: QualifiedBool qualified, michael@0: MutableHandleValue vp, bool strict); michael@0: michael@0: bool michael@0: baseops::SetElementHelper(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return baseops::SetPropertyHelper(cx, obj, receiver, id, Qualified, vp, michael@0: strict); michael@0: } michael@0: michael@0: bool michael@0: baseops::GetAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) michael@0: { michael@0: RootedObject nobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!baseops::LookupProperty(cx, obj, id, &nobj, &shape)) michael@0: return false; michael@0: if (!shape) { michael@0: *attrsp = 0; michael@0: return true; michael@0: } michael@0: if (!nobj->isNative()) michael@0: return JSObject::getGenericAttributes(cx, nobj, id, attrsp); michael@0: michael@0: *attrsp = GetShapeAttributes(nobj, shape); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: baseops::SetAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) michael@0: { michael@0: RootedObject nobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!baseops::LookupProperty(cx, obj, id, &nobj, &shape)) michael@0: return false; michael@0: if (!shape) michael@0: return true; michael@0: if (nobj->isNative() && IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: if (nobj->is()) { michael@0: if (*attrsp == (JSPROP_ENUMERATE | JSPROP_PERMANENT)) michael@0: return true; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_ARRAY_ATTRS); michael@0: return false; michael@0: } michael@0: if (!JSObject::sparsifyDenseElement(cx, nobj, JSID_TO_INT(id))) michael@0: return false; michael@0: shape = obj->nativeLookup(cx, id); michael@0: } michael@0: if (nobj->isNative()) { michael@0: if (!JSObject::changePropertyAttributes(cx, nobj, shape, *attrsp)) michael@0: return false; michael@0: if (*attrsp & JSPROP_READONLY) michael@0: MarkTypePropertyNonWritable(cx, obj, id); michael@0: return true; michael@0: } else { michael@0: return JSObject::setGenericAttributes(cx, nobj, id, attrsp); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: baseops::DeleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded) michael@0: { michael@0: RootedObject proto(cx); michael@0: RootedShape shape(cx); michael@0: if (!baseops::LookupProperty(cx, obj, id, &proto, &shape)) michael@0: return false; michael@0: if (!shape || proto != obj) { michael@0: /* michael@0: * If no property, or the property comes from a prototype, call the michael@0: * class's delProperty hook, passing succeeded as the result parameter. michael@0: */ michael@0: return CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded); michael@0: } michael@0: michael@0: GCPoke(cx->runtime()); michael@0: michael@0: if (IsImplicitDenseOrTypedArrayElement(shape)) { michael@0: if (obj->is()) { michael@0: // Don't delete elements from typed arrays. michael@0: *succeeded = false; michael@0: return true; michael@0: } michael@0: michael@0: if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded)) michael@0: return false; michael@0: if (!succeeded) michael@0: return true; michael@0: michael@0: obj->setDenseElementHole(cx, JSID_TO_INT(id)); michael@0: return js_SuppressDeletedProperty(cx, obj, id); michael@0: } michael@0: michael@0: if (!shape->configurable()) { michael@0: *succeeded = false; michael@0: return true; michael@0: } michael@0: michael@0: RootedId propid(cx, shape->propid()); michael@0: if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, propid, succeeded)) michael@0: return false; michael@0: if (!succeeded) michael@0: return true; michael@0: michael@0: return obj->removeProperty(cx, id) && js_SuppressDeletedProperty(cx, obj, id); michael@0: } michael@0: michael@0: bool michael@0: baseops::DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: bool *succeeded) michael@0: { michael@0: Rooted id(cx, NameToId(name)); michael@0: return baseops::DeleteGeneric(cx, obj, id, succeeded); michael@0: } michael@0: michael@0: bool michael@0: baseops::DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, bool *succeeded) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return baseops::DeleteGeneric(cx, obj, id, succeeded); michael@0: } michael@0: michael@0: bool michael@0: js::WatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable) michael@0: { michael@0: RootedObject obj(cx, GetInnerObject(cx, origObj)); michael@0: if (obj->isNative()) { michael@0: // Use sparse indexes for watched objects, as dense elements can be michael@0: // written to without checking the watchpoint map. michael@0: if (!JSObject::sparsifyDenseElements(cx, obj)) michael@0: return false; michael@0: michael@0: types::MarkTypePropertyNonData(cx, obj, id); michael@0: } michael@0: michael@0: WatchpointMap *wpmap = cx->compartment()->watchpointMap; michael@0: if (!wpmap) { michael@0: wpmap = cx->runtime()->new_(); michael@0: if (!wpmap || !wpmap->init()) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: cx->compartment()->watchpointMap = wpmap; michael@0: } michael@0: michael@0: return wpmap->watch(cx, obj, id, js::WatchHandler, callable); michael@0: } michael@0: michael@0: bool michael@0: baseops::Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable) michael@0: { michael@0: if (!obj->isNative() || obj->is()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_WATCH, michael@0: obj->getClass()->name); michael@0: return false; michael@0: } michael@0: michael@0: return WatchGuts(cx, obj, id, callable); michael@0: } michael@0: michael@0: bool michael@0: js::UnwatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id) michael@0: { michael@0: // Looking in the map for an unsupported object will never hit, so we don't michael@0: // need to check for nativeness or watchable-ness here. michael@0: RootedObject obj(cx, GetInnerObject(cx, origObj)); michael@0: if (WatchpointMap *wpmap = cx->compartment()->watchpointMap) michael@0: wpmap->unwatch(obj, id, nullptr, nullptr); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: baseops::Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id) michael@0: { michael@0: return UnwatchGuts(cx, obj, id); michael@0: } michael@0: michael@0: bool michael@0: js::HasDataProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp) michael@0: { michael@0: if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { michael@0: *vp = obj->getDenseElement(JSID_TO_INT(id)); michael@0: return true; michael@0: } michael@0: michael@0: if (Shape *shape = obj->nativeLookup(cx, id)) { michael@0: if (shape->hasDefaultGetter() && shape->hasSlot()) { michael@0: *vp = obj->nativeGetSlot(shape->slot()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Gets |obj[id]|. If that value's not callable, returns true and stores a michael@0: * non-primitive value in *vp. If it's callable, calls it with no arguments michael@0: * and |obj| as |this|, returning the result in *vp. michael@0: * michael@0: * This is a mini-abstraction for ES5 8.12.8 [[DefaultValue]], either steps 1-2 michael@0: * or steps 3-4. michael@0: */ michael@0: static bool michael@0: MaybeCallMethod(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) michael@0: { michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, vp)) michael@0: return false; michael@0: if (!js_IsCallable(vp)) { michael@0: vp.setObject(*obj); michael@0: return true; michael@0: } michael@0: return Invoke(cx, ObjectValue(*obj), vp, 0, nullptr, vp); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js::DefaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp) michael@0: { michael@0: JS_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID); michael@0: michael@0: Rooted id(cx); michael@0: michael@0: const Class *clasp = obj->getClass(); michael@0: if (hint == JSTYPE_STRING) { michael@0: id = NameToId(cx->names().toString); michael@0: michael@0: /* Optimize (new String(...)).toString(). */ michael@0: if (clasp == &StringObject::class_) { michael@0: if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) { michael@0: vp.setString(obj->as().unbox()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (!MaybeCallMethod(cx, obj, id, vp)) michael@0: return false; michael@0: if (vp.isPrimitive()) michael@0: return true; michael@0: michael@0: id = NameToId(cx->names().valueOf); michael@0: if (!MaybeCallMethod(cx, obj, id, vp)) michael@0: return false; michael@0: if (vp.isPrimitive()) michael@0: return true; michael@0: } else { michael@0: michael@0: /* Optimize new String(...).valueOf(). */ michael@0: if (clasp == &StringObject::class_) { michael@0: id = NameToId(cx->names().valueOf); michael@0: if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) { michael@0: vp.setString(obj->as().unbox()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: /* Optimize new Number(...).valueOf(). */ michael@0: if (clasp == &NumberObject::class_) { michael@0: id = NameToId(cx->names().valueOf); michael@0: if (ClassMethodIsNative(cx, obj, &NumberObject::class_, id, js_num_valueOf)) { michael@0: vp.setNumber(obj->as().unbox()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: id = NameToId(cx->names().valueOf); michael@0: if (!MaybeCallMethod(cx, obj, id, vp)) michael@0: return false; michael@0: if (vp.isPrimitive()) michael@0: return true; michael@0: michael@0: id = NameToId(cx->names().toString); michael@0: if (!MaybeCallMethod(cx, obj, id, vp)) michael@0: return false; michael@0: if (vp.isPrimitive()) michael@0: return true; michael@0: } michael@0: michael@0: /* Avoid recursive death when decompiling in js_ReportValueError. */ michael@0: RootedString str(cx); michael@0: if (hint == JSTYPE_STRING) { michael@0: str = JS_InternString(cx, clasp->name); michael@0: if (!str) michael@0: return false; michael@0: } else { michael@0: str = nullptr; michael@0: } michael@0: michael@0: RootedValue val(cx, ObjectValue(*obj)); michael@0: js_ReportValueError2(cx, JSMSG_CANT_CONVERT_TO, JSDVG_SEARCH_STACK, val, str, michael@0: (hint == JSTYPE_VOID) ? "primitive type" : TypeStrings[hint]); michael@0: return false; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: JS_EnumerateState(JSContext *cx, HandleObject obj, JSIterateOp enum_op, michael@0: MutableHandleValue statep, JS::MutableHandleId idp) michael@0: { michael@0: /* If the class has a custom JSCLASS_NEW_ENUMERATE hook, call it. */ michael@0: const Class *clasp = obj->getClass(); michael@0: JSEnumerateOp enumerate = clasp->enumerate; michael@0: if (clasp->flags & JSCLASS_NEW_ENUMERATE) { michael@0: JS_ASSERT(enumerate != JS_EnumerateStub); michael@0: return ((JSNewEnumerateOp) enumerate)(cx, obj, enum_op, statep, idp); michael@0: } michael@0: michael@0: if (!enumerate(cx, obj)) michael@0: return false; michael@0: michael@0: /* Tell InitNativeIterator to treat us like a native object. */ michael@0: JS_ASSERT(enum_op == JSENUMERATE_INIT || enum_op == JSENUMERATE_INIT_ALL); michael@0: statep.setMagic(JS_NATIVE_ENUMERATE); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::IsDelegate(JSContext *cx, HandleObject obj, const js::Value &v, bool *result) michael@0: { michael@0: if (v.isPrimitive()) { michael@0: *result = false; michael@0: return true; michael@0: } michael@0: return IsDelegateOfObject(cx, obj, &v.toObject(), result); michael@0: } michael@0: michael@0: bool michael@0: js::IsDelegateOfObject(JSContext *cx, HandleObject protoObj, JSObject* obj, bool *result) michael@0: { michael@0: RootedObject obj2(cx, obj); michael@0: for (;;) { michael@0: if (!JSObject::getProto(cx, obj2, &obj2)) michael@0: return false; michael@0: if (!obj2) { michael@0: *result = false; michael@0: return true; michael@0: } michael@0: if (obj2 == protoObj) { michael@0: *result = true; michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: JSObject * michael@0: js::GetBuiltinPrototypePure(GlobalObject *global, JSProtoKey protoKey) michael@0: { michael@0: JS_ASSERT(JSProto_Null <= protoKey); michael@0: JS_ASSERT(protoKey < JSProto_LIMIT); michael@0: michael@0: if (protoKey != JSProto_Null) { michael@0: const Value &v = global->getPrototype(protoKey); michael@0: if (v.isObject()) michael@0: return &v.toObject(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * The first part of this function has been hand-expanded and optimized into michael@0: * NewBuiltinClassInstance in jsobjinlines.h. michael@0: */ michael@0: bool michael@0: js::FindClassPrototype(ExclusiveContext *cx, MutableHandleObject protop, const Class *clasp) michael@0: { michael@0: protop.set(nullptr); michael@0: JSProtoKey protoKey = GetClassProtoKey(clasp); michael@0: if (protoKey != JSProto_Null) michael@0: return GetBuiltinPrototype(cx, protoKey, protop); michael@0: michael@0: RootedObject ctor(cx); michael@0: if (!FindClassObject(cx, &ctor, clasp)) michael@0: return false; michael@0: michael@0: if (ctor && ctor->is()) { michael@0: RootedValue v(cx); michael@0: if (cx->isJSContext()) { michael@0: if (!JSObject::getProperty(cx->asJSContext(), michael@0: ctor, ctor, cx->names().prototype, &v)) michael@0: { michael@0: return false; michael@0: } michael@0: } else { michael@0: Shape *shape = ctor->nativeLookup(cx, cx->names().prototype); michael@0: if (!shape || !NativeGetPureInline(ctor, shape, v.address())) michael@0: return false; michael@0: } michael@0: if (v.isObject()) michael@0: protop.set(&v.toObject()); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js::PrimitiveToObject(JSContext *cx, const Value &v) michael@0: { michael@0: if (v.isString()) { michael@0: Rooted str(cx, v.toString()); michael@0: return StringObject::create(cx, str); michael@0: } michael@0: if (v.isNumber()) michael@0: return NumberObject::create(cx, v.toNumber()); michael@0: michael@0: JS_ASSERT(v.isBoolean()); michael@0: return BooleanObject::create(cx, v.toBoolean()); michael@0: } michael@0: michael@0: /* Callers must handle the already-object case . */ michael@0: JSObject * michael@0: js::ToObjectSlow(JSContext *cx, HandleValue val, bool reportScanStack) michael@0: { michael@0: JS_ASSERT(!val.isMagic()); michael@0: JS_ASSERT(!val.isObject()); michael@0: michael@0: if (val.isNullOrUndefined()) { michael@0: if (reportScanStack) { michael@0: js_ReportIsNullOrUndefined(cx, JSDVG_SEARCH_STACK, val, NullPtr()); michael@0: } else { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, michael@0: val.isNull() ? "null" : "undefined", "object"); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: return PrimitiveToObject(cx, val); michael@0: } michael@0: michael@0: void michael@0: js_GetObjectSlotName(JSTracer *trc, char *buf, size_t bufsize) michael@0: { michael@0: JS_ASSERT(trc->debugPrinter() == js_GetObjectSlotName); michael@0: michael@0: JSObject *obj = (JSObject *)trc->debugPrintArg(); michael@0: uint32_t slot = uint32_t(trc->debugPrintIndex()); michael@0: michael@0: Shape *shape; michael@0: if (obj->isNative()) { michael@0: shape = obj->lastProperty(); michael@0: while (shape && (!shape->hasSlot() || shape->slot() != slot)) michael@0: shape = shape->previous(); michael@0: } else { michael@0: shape = nullptr; michael@0: } michael@0: michael@0: if (!shape) { michael@0: const char *slotname = nullptr; michael@0: if (obj->is()) { michael@0: #define TEST_SLOT_MATCHES_PROTOTYPE(name,code,init,clasp) \ michael@0: if ((code) == slot) { slotname = js_##name##_str; goto found; } michael@0: JS_FOR_EACH_PROTOTYPE(TEST_SLOT_MATCHES_PROTOTYPE) michael@0: #undef TEST_SLOT_MATCHES_PROTOTYPE michael@0: } michael@0: found: michael@0: if (slotname) michael@0: JS_snprintf(buf, bufsize, "CLASS_OBJECT(%s)", slotname); michael@0: else michael@0: JS_snprintf(buf, bufsize, "**UNKNOWN SLOT %ld**", (long)slot); michael@0: } else { michael@0: jsid propid = shape->propid(); michael@0: if (JSID_IS_INT(propid)) { michael@0: JS_snprintf(buf, bufsize, "%ld", (long)JSID_TO_INT(propid)); michael@0: } else if (JSID_IS_ATOM(propid)) { michael@0: PutEscapedString(buf, bufsize, JSID_TO_ATOM(propid), 0); michael@0: } else { michael@0: JS_snprintf(buf, bufsize, "**FINALIZED ATOM KEY**"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: js_ReportGetterOnlyAssignment(JSContext *cx, bool strict) michael@0: { michael@0: return JS_ReportErrorFlagsAndNumber(cx, michael@0: strict michael@0: ? JSREPORT_ERROR michael@0: : JSREPORT_WARNING | JSREPORT_STRICT, michael@0: js_GetErrorMessage, nullptr, michael@0: JSMSG_GETTER_ONLY); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_GetterOnlyPropertyStub(JSContext *cx, HandleObject obj, HandleId id, bool strict, michael@0: MutableHandleValue vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_GETTER_ONLY); michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: /* michael@0: * Routines to print out values during debugging. These are FRIEND_API to help michael@0: * the debugger find them and to support temporarily hacking js_Dump* calls michael@0: * into other code. michael@0: */ michael@0: michael@0: static void michael@0: dumpValue(const Value &v) michael@0: { michael@0: if (v.isNull()) michael@0: fprintf(stderr, "null"); michael@0: else if (v.isUndefined()) michael@0: fprintf(stderr, "undefined"); michael@0: else if (v.isInt32()) michael@0: fprintf(stderr, "%d", v.toInt32()); michael@0: else if (v.isDouble()) michael@0: fprintf(stderr, "%g", v.toDouble()); michael@0: else if (v.isString()) michael@0: v.toString()->dump(); michael@0: else if (v.isObject() && v.toObject().is()) { michael@0: JSFunction *fun = &v.toObject().as(); michael@0: if (fun->displayAtom()) { michael@0: fputs("displayAtom(), 0); michael@0: } else { michael@0: fputs("hasScript()) { michael@0: JSScript *script = fun->nonLazyScript(); michael@0: fprintf(stderr, " (%s:%d)", michael@0: script->filename() ? script->filename() : "", (int) script->lineno()); michael@0: } michael@0: fprintf(stderr, " at %p>", (void *) fun); michael@0: } else if (v.isObject()) { michael@0: JSObject *obj = &v.toObject(); michael@0: const Class *clasp = obj->getClass(); michael@0: fprintf(stderr, "<%s%s at %p>", michael@0: clasp->name, michael@0: (clasp == &JSObject::class_) ? "" : " object", michael@0: (void *) obj); michael@0: } else if (v.isBoolean()) { michael@0: if (v.toBoolean()) michael@0: fprintf(stderr, "true"); michael@0: else michael@0: fprintf(stderr, "false"); michael@0: } else if (v.isMagic()) { michael@0: fprintf(stderr, ""); michael@0: } else { michael@0: fprintf(stderr, "unexpected value"); michael@0: } michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js_DumpValue(const Value &val) michael@0: { michael@0: dumpValue(val); michael@0: fputc('\n', stderr); michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js_DumpId(jsid id) michael@0: { michael@0: fprintf(stderr, "jsid %p = ", (void *) JSID_BITS(id)); michael@0: dumpValue(IdToValue(id)); michael@0: fputc('\n', stderr); michael@0: } michael@0: michael@0: static void michael@0: DumpProperty(JSObject *obj, Shape &shape) michael@0: { michael@0: jsid id = shape.propid(); michael@0: uint8_t attrs = shape.attributes(); michael@0: michael@0: fprintf(stderr, " ((JSShape *) %p) ", (void *) &shape); michael@0: if (attrs & JSPROP_ENUMERATE) fprintf(stderr, "enumerate "); michael@0: if (attrs & JSPROP_READONLY) fprintf(stderr, "readonly "); michael@0: if (attrs & JSPROP_PERMANENT) fprintf(stderr, "permanent "); michael@0: if (attrs & JSPROP_SHARED) fprintf(stderr, "shared "); michael@0: michael@0: if (shape.hasGetterValue()) michael@0: fprintf(stderr, "getterValue=%p ", (void *) shape.getterObject()); michael@0: else if (!shape.hasDefaultGetter()) michael@0: fprintf(stderr, "getterOp=%p ", JS_FUNC_TO_DATA_PTR(void *, shape.getterOp())); michael@0: michael@0: if (shape.hasSetterValue()) michael@0: fprintf(stderr, "setterValue=%p ", (void *) shape.setterObject()); michael@0: else if (!shape.hasDefaultSetter()) michael@0: fprintf(stderr, "setterOp=%p ", JS_FUNC_TO_DATA_PTR(void *, shape.setterOp())); michael@0: michael@0: if (JSID_IS_ATOM(id)) michael@0: JSID_TO_STRING(id)->dump(); michael@0: else if (JSID_IS_INT(id)) michael@0: fprintf(stderr, "%d", (int) JSID_TO_INT(id)); michael@0: else michael@0: fprintf(stderr, "unknown jsid %p", (void *) JSID_BITS(id)); michael@0: michael@0: uint32_t slot = shape.hasSlot() ? shape.maybeSlot() : SHAPE_INVALID_SLOT; michael@0: fprintf(stderr, ": slot %d", slot); michael@0: if (shape.hasSlot()) { michael@0: fprintf(stderr, " = "); michael@0: dumpValue(obj->getSlot(slot)); michael@0: } else if (slot != SHAPE_INVALID_SLOT) { michael@0: fprintf(stderr, " (INVALID!)"); michael@0: } michael@0: fprintf(stderr, "\n"); michael@0: } michael@0: michael@0: bool michael@0: JSObject::uninlinedIsProxy() const michael@0: { michael@0: return is(); michael@0: } michael@0: michael@0: void michael@0: JSObject::dump() michael@0: { michael@0: JSObject *obj = this; michael@0: fprintf(stderr, "object %p\n", (void *) obj); michael@0: const Class *clasp = obj->getClass(); michael@0: fprintf(stderr, "class %p %s\n", (const void *)clasp, clasp->name); michael@0: michael@0: fprintf(stderr, "flags:"); michael@0: if (obj->isDelegate()) fprintf(stderr, " delegate"); michael@0: if (!obj->is() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible"); michael@0: if (obj->isIndexed()) fprintf(stderr, " indexed"); michael@0: if (obj->isBoundFunction()) fprintf(stderr, " bound_function"); michael@0: if (obj->isVarObj()) fprintf(stderr, " varobj"); michael@0: if (obj->watched()) fprintf(stderr, " watched"); michael@0: if (obj->isIteratedSingleton()) fprintf(stderr, " iterated_singleton"); michael@0: if (obj->isNewTypeUnknown()) fprintf(stderr, " new_type_unknown"); michael@0: if (obj->hasUncacheableProto()) fprintf(stderr, " has_uncacheable_proto"); michael@0: if (obj->hadElementsAccess()) fprintf(stderr, " had_elements_access"); michael@0: michael@0: if (obj->isNative()) { michael@0: if (obj->inDictionaryMode()) michael@0: fprintf(stderr, " inDictionaryMode"); michael@0: if (obj->hasShapeTable()) michael@0: fprintf(stderr, " hasShapeTable"); michael@0: } michael@0: fprintf(stderr, "\n"); michael@0: michael@0: if (obj->isNative()) { michael@0: uint32_t slots = obj->getDenseInitializedLength(); michael@0: if (slots) { michael@0: fprintf(stderr, "elements\n"); michael@0: for (uint32_t i = 0; i < slots; i++) { michael@0: fprintf(stderr, " %3d: ", i); michael@0: dumpValue(obj->getDenseElement(i)); michael@0: fprintf(stderr, "\n"); michael@0: fflush(stderr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: fprintf(stderr, "proto "); michael@0: TaggedProto proto = obj->getTaggedProto(); michael@0: if (proto.isLazy()) michael@0: fprintf(stderr, ""); michael@0: else michael@0: dumpValue(ObjectOrNullValue(proto.toObjectOrNull())); michael@0: fputc('\n', stderr); michael@0: michael@0: fprintf(stderr, "parent "); michael@0: dumpValue(ObjectOrNullValue(obj->getParent())); michael@0: fputc('\n', stderr); michael@0: michael@0: if (clasp->flags & JSCLASS_HAS_PRIVATE) michael@0: fprintf(stderr, "private %p\n", obj->getPrivate()); michael@0: michael@0: if (!obj->isNative()) michael@0: fprintf(stderr, "not native\n"); michael@0: michael@0: uint32_t reservedEnd = JSCLASS_RESERVED_SLOTS(clasp); michael@0: uint32_t slots = obj->slotSpan(); michael@0: uint32_t stop = obj->isNative() ? reservedEnd : slots; michael@0: if (stop > 0) michael@0: fprintf(stderr, obj->isNative() ? "reserved slots:\n" : "slots:\n"); michael@0: for (uint32_t i = 0; i < stop; i++) { michael@0: fprintf(stderr, " %3d ", i); michael@0: if (i < reservedEnd) michael@0: fprintf(stderr, "(reserved) "); michael@0: fprintf(stderr, "= "); michael@0: dumpValue(obj->getSlot(i)); michael@0: fputc('\n', stderr); michael@0: } michael@0: michael@0: if (obj->isNative()) { michael@0: fprintf(stderr, "properties:\n"); michael@0: Vector props; michael@0: for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) michael@0: props.append(&r.front()); michael@0: for (size_t i = props.length(); i-- != 0;) michael@0: DumpProperty(obj, *props[i]); michael@0: } michael@0: fputc('\n', stderr); michael@0: } michael@0: michael@0: static void michael@0: MaybeDumpObject(const char *name, JSObject *obj) michael@0: { michael@0: if (obj) { michael@0: fprintf(stderr, " %s: ", name); michael@0: dumpValue(ObjectValue(*obj)); michael@0: fputc('\n', stderr); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: MaybeDumpValue(const char *name, const Value &v) michael@0: { michael@0: if (!v.isNull()) { michael@0: fprintf(stderr, " %s: ", name); michael@0: dumpValue(v); michael@0: fputc('\n', stderr); michael@0: } michael@0: } michael@0: michael@0: JS_FRIEND_API(void) michael@0: js_DumpInterpreterFrame(JSContext *cx, InterpreterFrame *start) michael@0: { michael@0: /* This should only called during live debugging. */ michael@0: ScriptFrameIter i(cx, ScriptFrameIter::GO_THROUGH_SAVED); michael@0: if (!start) { michael@0: if (i.done()) { michael@0: fprintf(stderr, "no stack for cx = %p\n", (void*) cx); michael@0: return; michael@0: } michael@0: } else { michael@0: while (!i.done() && !i.isJit() && i.interpFrame() != start) michael@0: ++i; michael@0: michael@0: if (i.done()) { michael@0: fprintf(stderr, "fp = %p not found in cx = %p\n", michael@0: (void *)start, (void *)cx); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: for (; !i.done(); ++i) { michael@0: if (i.isJit()) michael@0: fprintf(stderr, "JIT frame\n"); michael@0: else michael@0: fprintf(stderr, "InterpreterFrame at %p\n", (void *) i.interpFrame()); michael@0: michael@0: if (i.isFunctionFrame()) { michael@0: fprintf(stderr, "callee fun: "); michael@0: dumpValue(i.calleev()); michael@0: } else { michael@0: fprintf(stderr, "global frame, no callee"); michael@0: } michael@0: fputc('\n', stderr); michael@0: michael@0: fprintf(stderr, "file %s line %u\n", michael@0: i.script()->filename(), (unsigned) i.script()->lineno()); michael@0: michael@0: if (jsbytecode *pc = i.pc()) { michael@0: fprintf(stderr, " pc = %p\n", pc); michael@0: fprintf(stderr, " current op: %s\n", js_CodeName[*pc]); michael@0: MaybeDumpObject("staticScope", i.script()->getStaticScope(pc)); michael@0: } michael@0: MaybeDumpValue("this", i.thisv()); michael@0: if (!i.isJit()) { michael@0: fprintf(stderr, " rval: "); michael@0: dumpValue(i.interpFrame()->returnValue()); michael@0: fputc('\n', stderr); michael@0: } michael@0: michael@0: fprintf(stderr, " flags:"); michael@0: if (i.isConstructing()) michael@0: fprintf(stderr, " constructing"); michael@0: if (!i.isJit() && i.interpFrame()->isDebuggerFrame()) michael@0: fprintf(stderr, " debugger"); michael@0: if (i.isEvalFrame()) michael@0: fprintf(stderr, " eval"); michael@0: if (!i.isJit() && i.interpFrame()->isYielding()) michael@0: fprintf(stderr, " yielding"); michael@0: if (!i.isJit() && i.interpFrame()->isGeneratorFrame()) michael@0: fprintf(stderr, " generator"); michael@0: fputc('\n', stderr); michael@0: michael@0: fprintf(stderr, " scopeChain: (JSObject *) %p\n", (void *) i.scopeChain()); michael@0: michael@0: fputc('\n', stderr); michael@0: } michael@0: } michael@0: michael@0: #endif /* DEBUG */ michael@0: michael@0: JS_FRIEND_API(void) michael@0: js_DumpBacktrace(JSContext *cx) michael@0: { michael@0: Sprinter sprinter(cx); michael@0: sprinter.init(); michael@0: size_t depth = 0; michael@0: for (ScriptFrameIter i(cx); !i.done(); ++i, ++depth) { michael@0: const char *filename = JS_GetScriptFilename(i.script()); michael@0: unsigned line = JS_PCToLineNumber(cx, i.script(), i.pc()); michael@0: JSScript *script = i.script(); michael@0: sprinter.printf("#%d %14p %s:%d (%p @ %d)\n", michael@0: depth, (i.isJit() ? 0 : i.interpFrame()), filename, line, michael@0: script, script->pcToOffset(i.pc())); michael@0: } michael@0: fprintf(stdout, "%s", sprinter.string()); michael@0: } michael@0: void michael@0: JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes) michael@0: { michael@0: if (hasDynamicSlots()) michael@0: sizes->mallocHeapSlots += mallocSizeOf(slots); michael@0: michael@0: if (hasDynamicElements()) { michael@0: js::ObjectElements *elements = getElementsHeader(); michael@0: sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements); michael@0: } michael@0: michael@0: // Other things may be measured in the future if DMD indicates it is worthwhile. michael@0: if (is() || michael@0: is() || michael@0: is() || michael@0: is() || michael@0: is() || michael@0: is()) michael@0: { michael@0: // Do nothing. But this function is hot, and we win by getting the michael@0: // common cases out of the way early. Some stats on the most common michael@0: // classes, as measured during a vanilla browser session: michael@0: // - (53.7%, 53.7%): Function michael@0: // - (18.0%, 71.7%): Object michael@0: // - (16.9%, 88.6%): Array michael@0: // - ( 3.9%, 92.5%): Call michael@0: // - ( 2.8%, 95.3%): RegExp michael@0: // - ( 1.0%, 96.4%): Proxy michael@0: michael@0: } else if (is()) { michael@0: sizes->mallocHeapArgumentsData += as().sizeOfMisc(mallocSizeOf); michael@0: } else if (is()) { michael@0: sizes->mallocHeapRegExpStatics += as().sizeOfData(mallocSizeOf); michael@0: } else if (is()) { michael@0: sizes->mallocHeapPropertyIteratorData += as().sizeOfMisc(mallocSizeOf); michael@0: } else if (is() || is()) { michael@0: ArrayBufferObject::addSizeOfExcludingThis(this, mallocSizeOf, sizes); michael@0: #ifdef JS_ION michael@0: } else if (is()) { michael@0: as().addSizeOfMisc(mallocSizeOf, &sizes->nonHeapCodeAsmJS, michael@0: &sizes->mallocHeapAsmJSModuleData); michael@0: #endif michael@0: #ifdef JS_HAS_CTYPES michael@0: } else { michael@0: // This must be the last case. michael@0: sizes->mallocHeapCtypesData += michael@0: js::SizeOfDataIfCDataObject(mallocSizeOf, const_cast(this)); michael@0: #endif michael@0: } michael@0: }