| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * JS object implementation. |
| */ |
| #include "jsobjinlines.h" |
| |
| #include <string.h> |
| |
| #include "mozilla/Util.h" |
| |
| #include "jstypes.h" |
| #include "jsutil.h" |
| #include "jsprf.h" |
| #include "jsapi.h" |
| #include "jsarray.h" |
| #include "jsatom.h" |
| #include "jscntxt.h" |
| #include "jsfun.h" |
| #include "jsgc.h" |
| #include "jsiter.h" |
| #include "jsnum.h" |
| #include "jsopcode.h" |
| #include "jsproxy.h" |
| #include "jsscript.h" |
| #include "jsstr.h" |
| #include "jsdbgapi.h" |
| #include "jswatchpoint.h" |
| #include "jswrapper.h" |
| #include "frontend/BytecodeCompiler.h" |
| #include "gc/Marking.h" |
| #include "jit/BaselineJIT.h" |
| #include "js/MemoryMetrics.h" |
| #include "vm/Interpreter.h" |
| #include "vm/Shape.h" |
| |
| #include "jsatominlines.h" |
| #include "jsboolinlines.h" |
| #include "jscntxtinlines.h" |
| #include "jscompartmentinlines.h" |
| #include "jstypedarrayinlines.h" |
| #include "builtin/Iterator-inl.h" |
| #include "vm/BooleanObject-inl.h" |
| #include "vm/NumberObject-inl.h" |
| #include "vm/RegExpStatics-inl.h" |
| #include "vm/Shape-inl.h" |
| #include "vm/StringObject-inl.h" |
| |
| using namespace js; |
| using namespace js::gc; |
| using namespace js::types; |
| |
| using js::frontend::IsIdentifier; |
| using mozilla::ArrayLength; |
| using mozilla::DebugOnly; |
| |
| JS_STATIC_ASSERT(int32_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value)) == int64_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value))); |
| |
| Class js::ObjectClass = { |
| js_Object_str, |
| JSCLASS_HAS_CACHED_PROTO(JSProto_Object), |
| JS_PropertyStub, /* addProperty */ |
| JS_DeletePropertyStub, /* delProperty */ |
| JS_PropertyStub, /* getProperty */ |
| JS_StrictPropertyStub, /* setProperty */ |
| JS_EnumerateStub, |
| JS_ResolveStub, |
| JS_ConvertStub |
| }; |
| |
| JS_FRIEND_API(JSObject *) |
| JS_ObjectToInnerObject(JSContext *cx, JSObject *objArg) |
| { |
| RootedObject obj(cx, objArg); |
| if (!obj) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INACTIVE); |
| return NULL; |
| } |
| return GetInnerObject(cx, obj); |
| } |
| |
| JS_FRIEND_API(JSObject *) |
| JS_ObjectToOuterObject(JSContext *cx, JSObject *obj_) |
| { |
| Rooted<JSObject*> obj(cx, obj_); |
| assertSameCompartment(cx, obj); |
| return GetOuterObject(cx, obj); |
| } |
| |
| JSObject * |
| js::NonNullObject(JSContext *cx, const Value &v) |
| { |
| if (v.isPrimitive()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT); |
| return NULL; |
| } |
| return &v.toObject(); |
| } |
| |
| const char * |
| js::InformalValueTypeName(const Value &v) |
| { |
| if (v.isObject()) |
| return v.toObject().getClass()->name; |
| if (v.isString()) |
| return "string"; |
| if (v.isNumber()) |
| return "number"; |
| if (v.isBoolean()) |
| return "boolean"; |
| if (v.isNull()) |
| return "null"; |
| if (v.isUndefined()) |
| return "undefined"; |
| return "value"; |
| } |
| |
| template <AllowGC allowGC> |
| JSBool |
| js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup, |
| typename MaybeRooted<JSObject*, allowGC>::HandleType obj, |
| typename MaybeRooted<jsid, allowGC>::HandleType id, |
| typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, |
| typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) |
| { |
| JSAutoResolveFlags rf(cx, 0); |
| if (lookup) { |
| if (!allowGC) |
| return false; |
| if (!lookup(cx, |
| MaybeRooted<JSObject*, allowGC>::toHandle(obj), |
| MaybeRooted<jsid, allowGC>::toHandle(id), |
| MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), |
| MaybeRooted<Shape*, allowGC>::toMutableHandle(propp))) |
| { |
| return false; |
| } |
| } else { |
| if (!baseops::LookupProperty<allowGC>(cx, obj, id, objp, propp)) |
| return false; |
| } |
| if (!propp) |
| return true; |
| |
| if (objp == obj) |
| return true; |
| |
| JSObject *outer = NULL; |
| if (JSObjectOp op = objp->getClass()->ext.outerObject) { |
| if (!allowGC) |
| return false; |
| RootedObject inner(cx, objp); |
| outer = op(cx, inner); |
| if (!outer) |
| return false; |
| } |
| |
| if (outer != objp) |
| propp.set(NULL); |
| return true; |
| } |
| |
| template JSBool |
| js::HasOwnProperty<CanGC>(JSContext *cx, LookupGenericOp lookup, |
| HandleObject obj, HandleId id, |
| MutableHandleObject objp, MutableHandleShape propp); |
| |
| template JSBool |
| js::HasOwnProperty<NoGC>(JSContext *cx, LookupGenericOp lookup, |
| JSObject *obj, jsid id, |
| FakeMutableHandle<JSObject*> objp, FakeMutableHandle<Shape*> propp); |
| |
| bool |
| js::NewPropertyDescriptorObject(JSContext *cx, const PropertyDescriptor *desc, |
| MutableHandleValue vp) |
| { |
| if (!desc->obj) { |
| vp.setUndefined(); |
| return true; |
| } |
| |
| /* We have our own property, so start creating the descriptor. */ |
| AutoPropDescRooter d(cx); |
| |
| d.initFromPropertyDescriptor(*desc); |
| if (!d.makeObject(cx)) |
| return false; |
| vp.set(d.pd()); |
| return true; |
| } |
| |
| void |
| PropDesc::initFromPropertyDescriptor(const PropertyDescriptor &desc) |
| { |
| isUndefined_ = false; |
| pd_.setUndefined(); |
| attrs = uint8_t(desc.attrs); |
| JS_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER))); |
| if (desc.attrs & (JSPROP_GETTER | JSPROP_SETTER)) { |
| hasGet_ = true; |
| get_ = ((desc.attrs & JSPROP_GETTER) && desc.getter) |
| ? CastAsObjectJsval(desc.getter) |
| : UndefinedValue(); |
| hasSet_ = true; |
| set_ = ((desc.attrs & JSPROP_SETTER) && desc.setter) |
| ? CastAsObjectJsval(desc.setter) |
| : UndefinedValue(); |
| hasValue_ = false; |
| value_.setUndefined(); |
| hasWritable_ = false; |
| } else { |
| hasGet_ = false; |
| get_.setUndefined(); |
| hasSet_ = false; |
| set_.setUndefined(); |
| hasValue_ = true; |
| value_ = desc.value; |
| hasWritable_ = true; |
| } |
| hasEnumerable_ = true; |
| hasConfigurable_ = true; |
| } |
| |
| bool |
| PropDesc::makeObject(JSContext *cx) |
| { |
| MOZ_ASSERT(!isUndefined()); |
| |
| RootedObject obj(cx, NewBuiltinClassInstance(cx, &ObjectClass)); |
| if (!obj) |
| return false; |
| |
| const JSAtomState &names = cx->names(); |
| RootedValue configurableVal(cx, BooleanValue((attrs & JSPROP_PERMANENT) == 0)); |
| RootedValue enumerableVal(cx, BooleanValue((attrs & JSPROP_ENUMERATE) != 0)); |
| RootedValue writableVal(cx, BooleanValue((attrs & JSPROP_READONLY) == 0)); |
| if ((hasConfigurable() && |
| !JSObject::defineProperty(cx, obj, names.configurable, configurableVal)) || |
| (hasEnumerable() && |
| !JSObject::defineProperty(cx, obj, names.enumerable, enumerableVal)) || |
| (hasGet() && |
| !JSObject::defineProperty(cx, obj, names.get, getterValue())) || |
| (hasSet() && |
| !JSObject::defineProperty(cx, obj, names.set, setterValue())) || |
| (hasValue() && |
| !JSObject::defineProperty(cx, obj, names.value, value())) || |
| (hasWritable() && |
| !JSObject::defineProperty(cx, obj, names.writable, writableVal))) |
| { |
| return false; |
| } |
| |
| pd_.setObject(*obj); |
| return true; |
| } |
| |
| bool |
| js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, |
| PropertyDescriptor *desc) |
| { |
| // FIXME: Call TrapGetOwnProperty directly once ScriptedIndirectProxies is removed |
| if (obj->isProxy()) |
| return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc, 0); |
| |
| RootedObject pobj(cx); |
| RootedShape shape(cx); |
| if (!HasOwnProperty<CanGC>(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) |
| return false; |
| if (!shape) { |
| desc->obj = NULL; |
| return true; |
| } |
| |
| bool doGet = true; |
| if (pobj->isNative()) { |
| desc->attrs = GetShapeAttributes(shape); |
| if (desc->attrs & (JSPROP_GETTER | JSPROP_SETTER)) { |
| doGet = false; |
| if (desc->attrs & JSPROP_GETTER) |
| desc->getter = CastAsPropertyOp(shape->getterObject()); |
| if (desc->attrs & JSPROP_SETTER) |
| desc->setter = CastAsStrictPropertyOp(shape->setterObject()); |
| } else { |
| // This is either a straight-up data property or (rarely) a |
| // property with a JSPropertyOp getter/setter. The latter must be |
| // reported to the caller as a plain data property, so don't |
| // populate desc.getter/setter, and mask away the SHARED bit. |
| desc->attrs &= ~JSPROP_SHARED; |
| } |
| } else { |
| if (!JSObject::getGenericAttributes(cx, pobj, id, &desc->attrs)) |
| return false; |
| } |
| |
| RootedValue value(cx); |
| if (doGet && !JSObject::getGeneric(cx, obj, obj, id, &value)) |
| return false; |
| |
| desc->value = value; |
| desc->obj = obj; |
| return true; |
| } |
| |
| bool |
| js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) |
| { |
| AutoPropertyDescriptorRooter desc(cx); |
| return GetOwnPropertyDescriptor(cx, obj, id, &desc) && |
| NewPropertyDescriptorObject(cx, &desc, vp); |
| } |
| |
| bool |
| js::GetFirstArgumentAsObject(JSContext *cx, const CallArgs &args, const char *method, |
| MutableHandleObject objp) |
| { |
| if (args.length() == 0) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, |
| method, "0", "s"); |
| return false; |
| } |
| |
| HandleValue v = args.handleAt(0); |
| if (!v.isObject()) { |
| char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); |
| if (!bytes) |
| return false; |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE, |
| bytes, "not an object"); |
| js_free(bytes); |
| return false; |
| } |
| |
| objp.set(&v.toObject()); |
| return true; |
| } |
| |
| static bool |
| HasProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, bool *foundp) |
| { |
| if (!JSObject::hasProperty(cx, obj, id, foundp, 0)) |
| return false; |
| if (!*foundp) { |
| vp.setUndefined(); |
| return true; |
| } |
| |
| /* |
| * We must go through the method read barrier in case id is 'get' or 'set'. |
| * There is no obvious way to defer cloning a joined function object whose |
| * identity will be used by DefinePropertyOnObject, e.g., or reflected via |
| * js::GetOwnPropertyDescriptor, as the getter or setter callable object. |
| */ |
| return !!JSObject::getGeneric(cx, obj, obj, id, vp); |
| } |
| |
| bool |
| PropDesc::initialize(JSContext *cx, const Value &origval, bool checkAccessors) |
| { |
| RootedValue v(cx, origval); |
| |
| /* 8.10.5 step 1 */ |
| if (v.isPrimitive()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT); |
| return false; |
| } |
| RootedObject desc(cx, &v.toObject()); |
| |
| /* Make a copy of the descriptor. We might need it later. */ |
| pd_ = v; |
| |
| isUndefined_ = false; |
| |
| /* |
| * Start with the proper defaults. XXX shouldn't be necessary when we get |
| * rid of PropDesc::attributes() |
| */ |
| attrs = JSPROP_PERMANENT | JSPROP_READONLY; |
| |
| bool found = false; |
| RootedId id(cx); |
| |
| /* 8.10.5 step 3 */ |
| id = NameToId(cx->names().enumerable); |
| if (!HasProperty(cx, desc, id, &v, &found)) |
| return false; |
| if (found) { |
| hasEnumerable_ = true; |
| if (ToBoolean(v)) |
| attrs |= JSPROP_ENUMERATE; |
| } |
| |
| /* 8.10.5 step 4 */ |
| id = NameToId(cx->names().configurable); |
| if (!HasProperty(cx, desc, id, &v, &found)) |
| return false; |
| if (found) { |
| hasConfigurable_ = true; |
| if (ToBoolean(v)) |
| attrs &= ~JSPROP_PERMANENT; |
| } |
| |
| /* 8.10.5 step 5 */ |
| id = NameToId(cx->names().value); |
| if (!HasProperty(cx, desc, id, &v, &found)) |
| return false; |
| if (found) { |
| hasValue_ = true; |
| value_ = v; |
| } |
| |
| /* 8.10.6 step 6 */ |
| id = NameToId(cx->names().writable); |
| if (!HasProperty(cx, desc, id, &v, &found)) |
| return false; |
| if (found) { |
| hasWritable_ = true; |
| if (ToBoolean(v)) |
| attrs &= ~JSPROP_READONLY; |
| } |
| |
| /* 8.10.7 step 7 */ |
| id = NameToId(cx->names().get); |
| if (!HasProperty(cx, desc, id, &v, &found)) |
| return false; |
| if (found) { |
| hasGet_ = true; |
| get_ = v; |
| attrs |= JSPROP_GETTER | JSPROP_SHARED; |
| attrs &= ~JSPROP_READONLY; |
| if (checkAccessors && !checkGetter(cx)) |
| return false; |
| } |
| |
| /* 8.10.7 step 8 */ |
| id = NameToId(cx->names().set); |
| if (!HasProperty(cx, desc, id, &v, &found)) |
| return false; |
| if (found) { |
| hasSet_ = true; |
| set_ = v; |
| attrs |= JSPROP_SETTER | JSPROP_SHARED; |
| attrs &= ~JSPROP_READONLY; |
| if (checkAccessors && !checkSetter(cx)) |
| return false; |
| } |
| |
| /* 8.10.7 step 9 */ |
| if ((hasGet() || hasSet()) && (hasValue() || hasWritable())) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INVALID_DESCRIPTOR); |
| return false; |
| } |
| |
| JS_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER))); |
| |
| return true; |
| } |
| |
| void |
| PropDesc::complete() |
| { |
| if (isGenericDescriptor() || isDataDescriptor()) { |
| if (!hasValue_) { |
| hasValue_ = true; |
| value_.setUndefined(); |
| } |
| if (!hasWritable_) { |
| hasWritable_ = true; |
| attrs |= JSPROP_READONLY; |
| } |
| } else { |
| if (!hasGet_) { |
| hasGet_ = true; |
| get_.setUndefined(); |
| } |
| if (!hasSet_) { |
| hasSet_ = true; |
| set_.setUndefined(); |
| } |
| } |
| if (!hasEnumerable_) { |
| hasEnumerable_ = true; |
| attrs &= ~JSPROP_ENUMERATE; |
| } |
| if (!hasConfigurable_) { |
| hasConfigurable_ = true; |
| attrs |= JSPROP_PERMANENT; |
| } |
| } |
| |
| bool |
| js::Throw(JSContext *cx, jsid id, unsigned errorNumber) |
| { |
| JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 1); |
| |
| JSString *idstr = IdToString(cx, id); |
| if (!idstr) |
| return false; |
| JSAutoByteString bytes(cx, idstr); |
| if (!bytes) |
| return false; |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber, bytes.ptr()); |
| return false; |
| } |
| |
| bool |
| js::Throw(JSContext *cx, JSObject *obj, unsigned errorNumber) |
| { |
| if (js_ErrorFormatString[errorNumber].argCount == 1) { |
| RootedValue val(cx, ObjectValue(*obj)); |
| js_ReportValueErrorFlags(cx, JSREPORT_ERROR, errorNumber, |
| JSDVG_IGNORE_STACK, val, NullPtr(), |
| NULL, NULL); |
| } else { |
| JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 0); |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber); |
| } |
| return false; |
| } |
| |
| static JSBool |
| Reject(JSContext *cx, unsigned errorNumber, bool throwError, jsid id, bool *rval) |
| { |
| if (throwError) |
| return Throw(cx, id, errorNumber); |
| |
| *rval = false; |
| return true; |
| } |
| |
| static JSBool |
| Reject(JSContext *cx, JSObject *obj, unsigned errorNumber, bool throwError, bool *rval) |
| { |
| if (throwError) |
| return Throw(cx, obj, errorNumber); |
| |
| *rval = false; |
| return JS_TRUE; |
| } |
| |
| static bool |
| Reject(JSContext *cx, HandleId id, unsigned errorNumber, bool throwError, bool *rval) |
| { |
| if (throwError) |
| return Throw(cx, id, errorNumber); |
| |
| *rval = false; |
| return true; |
| } |
| |
| // See comments on CheckDefineProperty in jsobj.h. |
| // |
| // DefinePropertyOnObject has its own implementation of these checks. |
| // |
| bool |
| js::CheckDefineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue value, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| if (!obj->isNative()) |
| return true; |
| |
| // ES5 8.12.9 Step 1. Even though we know obj is native, we use generic |
| // APIs for shorter, more readable code. |
| AutoPropertyDescriptorRooter desc(cx); |
| if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) |
| return false; |
| |
| // This does not have to check obj->isExtensible() when !desc.obj (steps |
| // 2-3) because the low-level methods JSObject::{add,put}Property check |
| // for that. |
| if (desc.obj && (desc.attrs & JSPROP_PERMANENT)) { |
| // Steps 6-11, skipping step 10.a.ii. Prohibit redefining a permanent |
| // property with different metadata, except to make a writable property |
| // non-writable. |
| if (getter != desc.getter || |
| setter != desc.setter || |
| (attrs != desc.attrs && attrs != (desc.attrs | JSPROP_READONLY))) |
| { |
| return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); |
| } |
| |
| // Step 10.a.ii. Prohibit changing the value of a non-configurable, |
| // non-writable data property. |
| if ((desc.attrs & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_READONLY)) == JSPROP_READONLY) { |
| bool same; |
| if (!SameValue(cx, value, desc.value, &same)) |
| return false; |
| if (!same) |
| return JSObject::reportReadOnly(cx, id); |
| } |
| } |
| return true; |
| } |
| |
| static JSBool |
| DefinePropertyOnObject(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, |
| bool throwError, bool *rval) |
| { |
| /* 8.12.9 step 1. */ |
| RootedShape shape(cx); |
| RootedObject obj2(cx); |
| JS_ASSERT(!obj->getOps()->lookupGeneric); |
| if (!HasOwnProperty<CanGC>(cx, NULL, obj, id, &obj2, &shape)) |
| return JS_FALSE; |
| |
| JS_ASSERT(!obj->getOps()->defineProperty); |
| |
| /* 8.12.9 steps 2-4. */ |
| if (!shape) { |
| if (!obj->isExtensible()) |
| return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); |
| |
| *rval = true; |
| |
| if (desc.isGenericDescriptor() || desc.isDataDescriptor()) { |
| JS_ASSERT(!obj->getOps()->defineProperty); |
| RootedValue v(cx, desc.hasValue() ? desc.value() : UndefinedValue()); |
| return baseops::DefineGeneric(cx, obj, id, v, |
| JS_PropertyStub, JS_StrictPropertyStub, |
| desc.attributes()); |
| } |
| |
| JS_ASSERT(desc.isAccessorDescriptor()); |
| |
| /* |
| * Getters and setters are just like watchpoints from an access |
| * control point of view. |
| */ |
| RootedValue dummy(cx); |
| unsigned dummyAttrs; |
| if (!CheckAccess(cx, obj, id, JSACC_WATCH, &dummy, &dummyAttrs)) |
| return JS_FALSE; |
| |
| RootedValue tmp(cx, UndefinedValue()); |
| return baseops::DefineGeneric(cx, obj, id, tmp, |
| desc.getter(), desc.setter(), desc.attributes()); |
| } |
| |
| /* 8.12.9 steps 5-6 (note 5 is merely a special case of 6). */ |
| RootedValue v(cx, UndefinedValue()); |
| |
| JS_ASSERT(obj == obj2); |
| |
| bool shapeDataDescriptor = true, |
| shapeAccessorDescriptor = false, |
| shapeWritable = true, |
| shapeConfigurable = true, |
| shapeEnumerable = true, |
| shapeHasDefaultGetter = true, |
| shapeHasDefaultSetter = true, |
| shapeHasGetterValue = false, |
| shapeHasSetterValue = false; |
| uint8_t shapeAttributes = JSPROP_ENUMERATE; |
| if (!IsImplicitDenseElement(shape)) { |
| shapeDataDescriptor = shape->isDataDescriptor(); |
| shapeAccessorDescriptor = shape->isAccessorDescriptor(); |
| shapeWritable = shape->writable(); |
| shapeConfigurable = shape->configurable(); |
| shapeEnumerable = shape->enumerable(); |
| shapeHasDefaultGetter = shape->hasDefaultGetter(); |
| shapeHasDefaultSetter = shape->hasDefaultSetter(); |
| shapeHasGetterValue = shape->hasGetterValue(); |
| shapeHasSetterValue = shape->hasSetterValue(); |
| shapeAttributes = shape->attributes(); |
| } |
| |
| do { |
| if (desc.isAccessorDescriptor()) { |
| if (!shapeAccessorDescriptor) |
| break; |
| |
| if (desc.hasGet()) { |
| bool same; |
| if (!SameValue(cx, desc.getterValue(), shape->getterOrUndefined(), &same)) |
| return false; |
| if (!same) |
| break; |
| } |
| |
| if (desc.hasSet()) { |
| bool same; |
| if (!SameValue(cx, desc.setterValue(), shape->setterOrUndefined(), &same)) |
| return false; |
| if (!same) |
| break; |
| } |
| } else { |
| /* |
| * Determine the current value of the property once, if the current |
| * value might actually need to be used or preserved later. NB: we |
| * guard on whether the current property is a data descriptor to |
| * avoid calling a getter; we won't need the value if it's not a |
| * data descriptor. |
| */ |
| if (IsImplicitDenseElement(shape)) { |
| v = obj->getDenseElement(JSID_TO_INT(id)); |
| } else if (shape->isDataDescriptor()) { |
| /* |
| * We must rule out a non-configurable js::PropertyOp-guarded |
| * property becoming a writable unguarded data property, since |
| * such a property can have its value changed to one the getter |
| * and setter preclude. |
| * |
| * A desc lacking writable but with value is a data descriptor |
| * and we must reject it as if it had writable: true if current |
| * is writable. |
| */ |
| if (!shape->configurable() && |
| (!shape->hasDefaultGetter() || !shape->hasDefaultSetter()) && |
| desc.isDataDescriptor() && |
| (desc.hasWritable() ? desc.writable() : shape->writable())) |
| { |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } |
| |
| if (!js_NativeGet(cx, obj, obj2, shape, 0, &v)) |
| return JS_FALSE; |
| } |
| |
| if (desc.isDataDescriptor()) { |
| if (!shapeDataDescriptor) |
| break; |
| |
| bool same; |
| if (desc.hasValue()) { |
| if (!SameValue(cx, desc.value(), v, &same)) |
| return false; |
| if (!same) { |
| /* |
| * Insist that a non-configurable js::PropertyOp data |
| * property is frozen at exactly the last-got value. |
| * |
| * Duplicate the first part of the big conjunction that |
| * we tested above, rather than add a local bool flag. |
| * Likewise, don't try to keep shape->writable() in a |
| * flag we veto from true to false for non-configurable |
| * PropertyOp-based data properties and test before the |
| * SameValue check later on in order to re-use that "if |
| * (!SameValue) Reject" logic. |
| * |
| * This function is large and complex enough that it |
| * seems best to repeat a small bit of code and return |
| * Reject(...) ASAP, instead of being clever. |
| */ |
| if (!shapeConfigurable && |
| (!shape->hasDefaultGetter() || !shape->hasDefaultSetter())) |
| { |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } |
| break; |
| } |
| } |
| if (desc.hasWritable() && desc.writable() != shapeWritable) |
| break; |
| } else { |
| /* The only fields in desc will be handled below. */ |
| JS_ASSERT(desc.isGenericDescriptor()); |
| } |
| } |
| |
| if (desc.hasConfigurable() && desc.configurable() != shapeConfigurable) |
| break; |
| if (desc.hasEnumerable() && desc.enumerable() != shapeEnumerable) |
| break; |
| |
| /* The conditions imposed by step 5 or step 6 apply. */ |
| *rval = true; |
| return true; |
| } while (0); |
| |
| /* 8.12.9 step 7. */ |
| if (!shapeConfigurable) { |
| if ((desc.hasConfigurable() && desc.configurable()) || |
| (desc.hasEnumerable() && desc.enumerable() != shape->enumerable())) { |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } |
| } |
| |
| bool callDelProperty = false; |
| |
| if (desc.isGenericDescriptor()) { |
| /* 8.12.9 step 8, no validation required */ |
| } else if (desc.isDataDescriptor() != shapeDataDescriptor) { |
| /* 8.12.9 step 9. */ |
| if (!shapeConfigurable) |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } else if (desc.isDataDescriptor()) { |
| /* 8.12.9 step 10. */ |
| JS_ASSERT(shapeDataDescriptor); |
| if (!shapeConfigurable && !shape->writable()) { |
| if (desc.hasWritable() && desc.writable()) |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| if (desc.hasValue()) { |
| bool same; |
| if (!SameValue(cx, desc.value(), v, &same)) |
| return false; |
| if (!same) |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } |
| } |
| |
| callDelProperty = !shapeHasDefaultGetter || !shapeHasDefaultSetter; |
| } else { |
| /* 8.12.9 step 11. */ |
| JS_ASSERT(desc.isAccessorDescriptor() && shape->isAccessorDescriptor()); |
| if (!shape->configurable()) { |
| if (desc.hasSet()) { |
| bool same; |
| if (!SameValue(cx, desc.setterValue(), shape->setterOrUndefined(), &same)) |
| return false; |
| if (!same) |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } |
| |
| if (desc.hasGet()) { |
| bool same; |
| if (!SameValue(cx, desc.getterValue(), shape->getterOrUndefined(), &same)) |
| return false; |
| if (!same) |
| return Reject(cx, JSMSG_CANT_REDEFINE_PROP, throwError, id, rval); |
| } |
| } |
| } |
| |
| /* 8.12.9 step 12. */ |
| unsigned attrs; |
| PropertyOp getter; |
| StrictPropertyOp setter; |
| if (desc.isGenericDescriptor()) { |
| unsigned changed = 0; |
| if (desc.hasConfigurable()) |
| changed |= JSPROP_PERMANENT; |
| if (desc.hasEnumerable()) |
| changed |= JSPROP_ENUMERATE; |
| |
| attrs = (shapeAttributes & ~changed) | (desc.attributes() & changed); |
| getter = IsImplicitDenseElement(shape) ? JS_PropertyStub : shape->getter(); |
| setter = IsImplicitDenseElement(shape) ? JS_StrictPropertyStub : shape->setter(); |
| } else if (desc.isDataDescriptor()) { |
| unsigned unchanged = 0; |
| if (!desc.hasConfigurable()) |
| unchanged |= JSPROP_PERMANENT; |
| if (!desc.hasEnumerable()) |
| unchanged |= JSPROP_ENUMERATE; |
| /* Watch out for accessor -> data transformations here. */ |
| if (!desc.hasWritable() && shapeDataDescriptor) |
| unchanged |= JSPROP_READONLY; |
| |
| if (desc.hasValue()) |
| v = desc.value(); |
| attrs = (desc.attributes() & ~unchanged) | (shapeAttributes & unchanged); |
| getter = JS_PropertyStub; |
| setter = JS_StrictPropertyStub; |
| } else { |
| JS_ASSERT(desc.isAccessorDescriptor()); |
| |
| /* |
| * Getters and setters are just like watchpoints from an access |
| * control point of view. |
| */ |
| RootedValue dummy(cx); |
| if (!CheckAccess(cx, obj2, id, JSACC_WATCH, &dummy, &attrs)) |
| return JS_FALSE; |
| |
| /* 8.12.9 step 12. */ |
| unsigned changed = 0; |
| if (desc.hasConfigurable()) |
| changed |= JSPROP_PERMANENT; |
| if (desc.hasEnumerable()) |
| changed |= JSPROP_ENUMERATE; |
| if (desc.hasGet()) |
| changed |= JSPROP_GETTER | JSPROP_SHARED | JSPROP_READONLY; |
| if (desc.hasSet()) |
| changed |= JSPROP_SETTER | JSPROP_SHARED | JSPROP_READONLY; |
| |
| attrs = (desc.attributes() & changed) | (shapeAttributes & ~changed); |
| if (desc.hasGet()) { |
| getter = desc.getter(); |
| } else { |
| getter = (shapeHasDefaultGetter && !shapeHasGetterValue) |
| ? JS_PropertyStub |
| : shape->getter(); |
| } |
| if (desc.hasSet()) { |
| setter = desc.setter(); |
| } else { |
| setter = (shapeHasDefaultSetter && !shapeHasSetterValue) |
| ? JS_StrictPropertyStub |
| : shape->setter(); |
| } |
| } |
| |
| *rval = true; |
| |
| /* |
| * Since "data" properties implemented using native C functions may rely on |
| * side effects during setting, we must make them aware that they have been |
| * "assigned"; deleting the property before redefining it does the trick. |
| * See bug 539766, where we ran into problems when we redefined |
| * arguments.length without making the property aware that its value had |
| * been changed (which would have happened if we had deleted it before |
| * redefining it or we had invoked its setter to change its value). |
| */ |
| if (callDelProperty) { |
| JSBool succeeded; |
| if (!CallJSDeletePropertyOp(cx, obj2->getClass()->delProperty, obj2, id, &succeeded)) |
| return false; |
| } |
| |
| return baseops::DefineGeneric(cx, obj, id, v, getter, setter, attrs); |
| } |
| |
| /* ES6 20130308 draft 8.4.2.1 [[DefineOwnProperty]] */ |
| static JSBool |
| DefinePropertyOnArray(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, |
| bool throwError, bool *rval) |
| { |
| JS_ASSERT(obj->isArray()); |
| |
| /* Step 2. */ |
| if (id == NameToId(cx->names().length)) { |
| // Canonicalize value, if necessary, before proceeding any further. It |
| // would be better if this were always/only done by ArraySetLength. |
| // But canonicalization may throw a RangeError (or other exception, if |
| // the value is an object with user-defined conversion semantics) |
| // before other attributes are checked. So as long as our internal |
| // defineProperty hook doesn't match the ECMA one, this duplicate |
| // checking can't be helped. |
| RootedValue v(cx); |
| if (desc.hasValue()) { |
| uint32_t newLen; |
| if (!CanonicalizeArrayLengthValue(cx, desc.value(), &newLen)) |
| return false; |
| v.setNumber(newLen); |
| } else { |
| v.setNumber(obj->getArrayLength()); |
| } |
| |
| if (desc.hasConfigurable() && desc.configurable()) |
| return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
| if (desc.hasEnumerable() && desc.enumerable()) |
| return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
| |
| if (desc.isAccessorDescriptor()) |
| return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
| |
| unsigned attrs = obj->nativeLookup(cx, id)->attributes(); |
| if (!obj->arrayLengthIsWritable()) { |
| if (desc.hasWritable() && desc.writable()) |
| return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval); |
| } else { |
| if (desc.hasWritable() && !desc.writable()) |
| attrs = attrs | JSPROP_READONLY; |
| } |
| |
| return ArraySetLength(cx, obj, id, attrs, v, throwError); |
| } |
| |
| /* Step 3. */ |
| uint32_t index; |
| if (js_IdIsIndex(id, &index)) { |
| /* Step 3b. */ |
| uint32_t oldLen = obj->getArrayLength(); |
| |
| /* Steps 3a, 3e. */ |
| if (index >= oldLen && !obj->arrayLengthIsWritable()) |
| return Reject(cx, obj, JSMSG_CANT_APPEND_TO_ARRAY, throwError, rval); |
| |
| /* Steps 3f-j. */ |
| return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval); |
| } |
| |
| /* Step 4. */ |
| return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval); |
| } |
| |
| bool |
| js::DefineProperty(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc, |
| bool throwError, bool *rval) |
| { |
| if (obj->isArray()) |
| return DefinePropertyOnArray(cx, obj, id, desc, throwError, rval); |
| |
| if (obj->getOps()->lookupGeneric) { |
| /* |
| * FIXME: Once ScriptedIndirectProxies are removed, this code should call |
| * TrapDefineOwnProperty directly |
| */ |
| if (obj->isProxy()) { |
| RootedValue pd(cx, desc.pd()); |
| return Proxy::defineProperty(cx, obj, id, pd); |
| } |
| return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); |
| } |
| |
| return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval); |
| } |
| |
| JSBool |
| js::DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue descriptor, |
| JSBool *bp) |
| { |
| AutoPropDescArrayRooter descs(cx); |
| PropDesc *desc = descs.append(); |
| if (!desc || !desc->initialize(cx, descriptor)) |
| return false; |
| |
| bool rval; |
| if (!DefineProperty(cx, obj, id, *desc, true, &rval)) |
| return false; |
| *bp = !!rval; |
| return true; |
| } |
| |
| JSBool |
| js::DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, |
| const PropertyDescriptor &descriptor, JSBool *bp) |
| { |
| AutoPropDescArrayRooter descs(cx); |
| PropDesc *desc = descs.append(); |
| if (!desc) |
| return false; |
| |
| desc->initFromPropertyDescriptor(descriptor); |
| |
| bool rval; |
| if (!DefineProperty(cx, obj, id, *desc, true, &rval)) |
| return false; |
| *bp = !!rval; |
| return true; |
| } |
| |
| |
| bool |
| js::ReadPropertyDescriptors(JSContext *cx, HandleObject props, bool checkAccessors, |
| AutoIdVector *ids, AutoPropDescArrayRooter *descs) |
| { |
| if (!GetPropertyNames(cx, props, JSITER_OWNONLY, ids)) |
| return false; |
| |
| RootedId id(cx); |
| for (size_t i = 0, len = ids->length(); i < len; i++) { |
| id = (*ids)[i]; |
| PropDesc* desc = descs->append(); |
| RootedValue v(cx); |
| if (!desc || |
| !JSObject::getGeneric(cx, props, props, id, &v) || |
| !desc->initialize(cx, v, checkAccessors)) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Duplicated in Object.cpp |
| static bool |
| DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) |
| { |
| AutoIdVector ids(cx); |
| AutoPropDescArrayRooter descs(cx); |
| if (!ReadPropertyDescriptors(cx, props, true, &ids, &descs)) |
| return false; |
| |
| bool dummy; |
| for (size_t i = 0, len = ids.length(); i < len; i++) { |
| if (!DefineProperty(cx, obj, ids.handleAt(i), descs[i], true, &dummy)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| extern JSBool |
| js_PopulateObject(JSContext *cx, HandleObject newborn, HandleObject props) |
| { |
| return DefineProperties(cx, newborn, props); |
| } |
| |
| /* static */ inline unsigned |
| JSObject::getSealedOrFrozenAttributes(unsigned attrs, ImmutabilityType it) |
| { |
| /* Make all attributes permanent; if freezing, make data attributes read-only. */ |
| if (it == FREEZE && !(attrs & (JSPROP_GETTER | JSPROP_SETTER))) |
| return JSPROP_PERMANENT | JSPROP_READONLY; |
| return JSPROP_PERMANENT; |
| } |
| |
| /* static */ bool |
| JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it) |
| { |
| assertSameCompartment(cx, obj); |
| JS_ASSERT(it == SEAL || it == FREEZE); |
| |
| if (obj->isExtensible() && !JSObject::preventExtensions(cx, obj)) |
| return false; |
| |
| AutoIdVector props(cx); |
| if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props)) |
| return false; |
| |
| /* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */ |
| JS_ASSERT_IF(obj->isNative(), obj->getDenseCapacity() == 0); |
| |
| if (obj->isNative() && !obj->inDictionaryMode()) { |
| /* |
| * Seal/freeze non-dictionary objects by constructing a new shape |
| * hierarchy mirroring the original one, which can be shared if many |
| * objects with the same structure are sealed/frozen. If we use the |
| * generic path below then any non-empty object will be converted to |
| * dictionary mode. |
| */ |
| RootedShape last(cx, EmptyShape::getInitialShape(cx, obj->getClass(), |
| obj->getTaggedProto(), |
| obj->getParent(), |
| obj->getMetadata(), |
| obj->numFixedSlots(), |
| obj->lastProperty()->getObjectFlags())); |
| if (!last) |
| return false; |
| |
| /* Get an in order list of the shapes in this object. */ |
| AutoShapeVector shapes(cx); |
| for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) { |
| if (!shapes.append(&r.front())) |
| return false; |
| } |
| Reverse(shapes.begin(), shapes.end()); |
| |
| for (size_t i = 0; i < shapes.length(); i++) { |
| StackShape child(shapes[i]); |
| StackShape::AutoRooter rooter(cx, &child); |
| child.attrs |= getSealedOrFrozenAttributes(child.attrs, it); |
| |
| if (!JSID_IS_EMPTY(child.propid)) |
| MarkTypePropertyConfigured(cx, obj, child.propid); |
| |
| last = cx->propertyTree().getChild(cx, last, obj->numFixedSlots(), child); |
| if (!last) |
| return false; |
| } |
| |
| JS_ASSERT(obj->lastProperty()->slotSpan() == last->slotSpan()); |
| JS_ALWAYS_TRUE(setLastProperty(cx, obj, last)); |
| } else { |
| RootedId id(cx); |
| for (size_t i = 0; i < props.length(); i++) { |
| id = props[i]; |
| |
| unsigned attrs; |
| if (!getGenericAttributes(cx, obj, id, &attrs)) |
| return false; |
| |
| unsigned new_attrs = getSealedOrFrozenAttributes(attrs, it); |
| |
| /* If we already have the attributes we need, skip the setAttributes call. */ |
| if ((attrs | new_attrs) == attrs) |
| continue; |
| |
| attrs |= new_attrs; |
| if (!setGenericAttributes(cx, obj, id, &attrs)) |
| return false; |
| } |
| } |
| |
| // Ordinarily ArraySetLength handles this, but we're going behind its back |
| // right now, so we must do this manually. Neither the custom property |
| // tree mutations nor the setGenericAttributes call in the above code will |
| // do this for us. |
| // |
| // ArraySetLength also implements the capacity <= length invariant for |
| // arrays with non-writable length. We don't need to do anything special |
| // for that, because capacity was zeroed out by preventExtensions. (See |
| // the assertion before the if-else above.) |
| if (it == FREEZE && obj->isArray()) |
| obj->getElementsHeader()->setNonwritableArrayLength(); |
| |
| return true; |
| } |
| |
| /* static */ bool |
| JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it, bool *resultp) |
| { |
| if (obj->isExtensible()) { |
| *resultp = false; |
| return true; |
| } |
| |
| AutoIdVector props(cx); |
| if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props)) |
| return false; |
| |
| RootedId id(cx); |
| for (size_t i = 0, len = props.length(); i < len; i++) { |
| id = props[i]; |
| |
| unsigned attrs; |
| if (!getGenericAttributes(cx, obj, id, &attrs)) |
| return false; |
| |
| /* |
| * If the property is configurable, this object is neither sealed nor |
| * frozen. If the property is a writable data property, this object is |
| * not frozen. |
| */ |
| if (!(attrs & JSPROP_PERMANENT) || |
| (it == FREEZE && !(attrs & (JSPROP_READONLY | JSPROP_GETTER | JSPROP_SETTER)))) |
| { |
| *resultp = false; |
| return true; |
| } |
| } |
| |
| /* All properties checked out. This object is sealed/frozen. */ |
| *resultp = true; |
| return true; |
| } |
| |
| /* static */ |
| const char * |
| JSObject::className(JSContext *cx, HandleObject obj) |
| { |
| assertSameCompartment(cx, obj); |
| |
| if (obj->isProxy()) |
| return Proxy::className(cx, obj); |
| |
| return obj->getClass()->name; |
| } |
| |
| /* |
| * Get the GC kind to use for scripted 'new' on the given class. |
| * FIXME bug 547327: estimate the size from the allocation site. |
| */ |
| static inline gc::AllocKind |
| NewObjectGCKind(js::Class *clasp) |
| { |
| if (clasp == &ArrayClass) |
| return gc::FINALIZE_OBJECT8; |
| if (clasp == &JSFunction::class_) |
| return gc::FINALIZE_OBJECT2; |
| return gc::FINALIZE_OBJECT4; |
| } |
| |
| static inline JSObject * |
| NewObject(JSContext *cx, Class *clasp, types::TypeObject *type_, JSObject *parent, |
| gc::AllocKind kind, NewObjectKind newKind) |
| { |
| JS_ASSERT(clasp != &ArrayClass); |
| JS_ASSERT_IF(clasp == &JSFunction::class_, |
| kind == JSFunction::FinalizeKind || kind == JSFunction::ExtendedFinalizeKind); |
| JS_ASSERT_IF(parent, &parent->global() == cx->compartment()->maybeGlobal()); |
| |
| RootedTypeObject type(cx, type_); |
| |
| RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(type->proto), |
| parent, NewObjectMetadata(cx), kind)); |
| if (!shape) |
| return NULL; |
| |
| gc::InitialHeap heap = GetInitialHeap(newKind, clasp); |
| JSObject *obj = JSObject::create(cx, kind, heap, shape, type); |
| if (!obj) |
| return NULL; |
| |
| if (newKind == SingletonObject) { |
| RootedObject nobj(cx, obj); |
| if (!JSObject::setSingletonType(cx, nobj)) |
| return NULL; |
| obj = nobj; |
| } |
| |
| /* |
| * This will cancel an already-running incremental GC from doing any more |
| * slices, and it will prevent any future incremental GCs. |
| */ |
| if (clasp->trace && !(clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS)) |
| cx->runtime()->gcIncrementalEnabled = false; |
| |
| Probes::createObject(cx, obj); |
| return obj; |
| } |
| |
| void |
| NewObjectCache::fillProto(EntryIndex entry, Class *clasp, js::TaggedProto proto, |
| gc::AllocKind kind, JSObject *obj) |
| { |
| JS_ASSERT_IF(proto.isObject(), !proto.toObject()->is<GlobalObject>()); |
| JS_ASSERT(obj->getTaggedProto() == proto); |
| return fill(entry, clasp, proto.raw(), kind, obj); |
| } |
| |
| JSObject * |
| js::NewObjectWithGivenProto(JSContext *cx, js::Class *clasp, |
| js::TaggedProto proto_, JSObject *parent_, |
| gc::AllocKind allocKind, NewObjectKind newKind) |
| { |
| Rooted<TaggedProto> proto(cx, proto_); |
| RootedObject parent(cx, parent_); |
| |
| if (CanBeFinalizedInBackground(allocKind, clasp)) |
| allocKind = GetBackgroundAllocKind(allocKind); |
| |
| NewObjectCache &cache = cx->runtime()->newObjectCache; |
| |
| NewObjectCache::EntryIndex entry = -1; |
| if (proto.isObject() && |
| newKind == GenericObject && |
| !cx->compartment()->objectMetadataCallback && |
| (!parent || parent == proto.toObject()->getParent()) && |
| !proto.toObject()->is<GlobalObject>()) |
| { |
| if (cache.lookupProto(clasp, proto.toObject(), allocKind, &entry)) { |
| JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp)); |
| if (obj) |
| return obj; |
| } |
| } |
| |
| types::TypeObject *type = cx->compartment()->getNewType(cx, clasp, proto, NULL); |
| if (!type) |
| return NULL; |
| |
| /* |
| * Default parent to the parent of the prototype, which was set from |
| * the parent of the prototype's constructor. |
| */ |
| if (!parent && proto.isObject()) |
| parent = proto.toObject()->getParent(); |
| |
| RootedObject obj(cx, NewObject(cx, clasp, type, parent, allocKind, newKind)); |
| if (!obj) |
| return NULL; |
| |
| if (entry != -1 && !obj->hasDynamicSlots()) |
| cache.fillProto(entry, clasp, proto, allocKind, obj); |
| |
| return obj; |
| } |
| |
| JSObject * |
| js::NewObjectWithClassProtoCommon(JSContext *cx, js::Class *clasp, JSObject *protoArg, JSObject *parentArg, |
| gc::AllocKind allocKind, NewObjectKind newKind) |
| { |
| if (protoArg) |
| return NewObjectWithGivenProto(cx, clasp, protoArg, parentArg, allocKind, newKind); |
| |
| if (CanBeFinalizedInBackground(allocKind, clasp)) |
| allocKind = GetBackgroundAllocKind(allocKind); |
| |
| if (!parentArg) |
| parentArg = cx->global(); |
| |
| /* |
| * Use the object cache, except for classes without a cached proto key. |
| * On these objects, FindProto will do a dynamic property lookup to get |
| * global[className].prototype, where changes to either the className or |
| * prototype property would render the cached lookup incorrect. For classes |
| * with a proto key, the prototype created during class initialization is |
| * stored in an immutable slot on the global (except for ClearScope, which |
| * will flush the new object cache). |
| */ |
| JSProtoKey protoKey = GetClassProtoKey(clasp); |
| |
| NewObjectCache &cache = cx->runtime()->newObjectCache; |
| |
| NewObjectCache::EntryIndex entry = -1; |
| if (parentArg->is<GlobalObject>() && |
| protoKey != JSProto_Null && |
| newKind == GenericObject && |
| !cx->compartment()->objectMetadataCallback) |
| { |
| if (cache.lookupGlobal(clasp, &parentArg->as<GlobalObject>(), allocKind, &entry)) { |
| JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp)); |
| if (obj) |
| return obj; |
| } |
| } |
| |
| RootedObject parent(cx, parentArg); |
| RootedObject proto(cx, protoArg); |
| |
| if (!FindProto(cx, clasp, &proto)) |
| return NULL; |
| |
| types::TypeObject *type = proto->getNewType(cx, clasp); |
| if (!type) |
| return NULL; |
| |
| JSObject *obj = NewObject(cx, clasp, type, parent, allocKind, newKind); |
| if (!obj) |
| return NULL; |
| |
| if (entry != -1 && !obj->hasDynamicSlots()) |
| cache.fillGlobal(entry, clasp, &parent->as<GlobalObject>(), allocKind, obj); |
| |
| return obj; |
| } |
| |
| JSObject * |
| js::NewObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, gc::AllocKind allocKind, |
| NewObjectKind newKind /* = GenericObject */) |
| { |
| JS_ASSERT(type->proto->hasNewType(&ObjectClass, type)); |
| JS_ASSERT(parent); |
| |
| JS_ASSERT(allocKind <= gc::FINALIZE_OBJECT_LAST); |
| if (CanBeFinalizedInBackground(allocKind, &ObjectClass)) |
| allocKind = GetBackgroundAllocKind(allocKind); |
| |
| NewObjectCache &cache = cx->runtime()->newObjectCache; |
| |
| NewObjectCache::EntryIndex entry = -1; |
| if (parent == type->proto->getParent() && |
| newKind == GenericObject && |
| !cx->compartment()->objectMetadataCallback) |
| { |
| if (cache.lookupType(&ObjectClass, type, allocKind, &entry)) { |
| JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, &ObjectClass)); |
| if (obj) |
| return obj; |
| } |
| } |
| |
| JSObject *obj = NewObject(cx, &ObjectClass, type, parent, allocKind, newKind); |
| if (!obj) |
| return NULL; |
| |
| if (entry != -1 && !obj->hasDynamicSlots()) |
| cache.fillType(entry, &ObjectClass, type, allocKind, obj); |
| |
| return obj; |
| } |
| |
| bool |
| js::NewObjectScriptedCall(JSContext *cx, MutableHandleObject pobj) |
| { |
| jsbytecode *pc; |
| RootedScript script(cx, cx->currentScript(&pc)); |
| gc::AllocKind allocKind = NewObjectGCKind(&ObjectClass); |
| NewObjectKind newKind = script |
| ? UseNewTypeForInitializer(cx, script, pc, &ObjectClass) |
| : GenericObject; |
| RootedObject obj(cx, NewBuiltinClassInstance(cx, &ObjectClass, allocKind, newKind)); |
| if (!obj) |
| return false; |
| |
| if (script) { |
| /* Try to specialize the type of the object to the scripted call site. */ |
| if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) |
| return false; |
| } |
| |
| pobj.set(obj); |
| return true; |
| } |
| |
| JSObject * |
| js::NewReshapedObject(JSContext *cx, HandleTypeObject type, JSObject *parent, |
| gc::AllocKind kind, HandleShape shape) |
| { |
| RootedObject res(cx, NewObjectWithType(cx, type, parent, kind)); |
| if (!res) |
| return NULL; |
| |
| if (shape->isEmptyShape()) |
| return res; |
| |
| /* Get all the ids in the object, in order. */ |
| js::AutoIdVector ids(cx); |
| { |
| for (unsigned i = 0; i <= shape->slot(); i++) { |
| if (!ids.append(JSID_VOID)) |
| return NULL; |
| } |
| Shape *nshape = shape; |
| while (!nshape->isEmptyShape()) { |
| ids[nshape->slot()] = nshape->propid(); |
| nshape = nshape->previous(); |
| } |
| } |
| |
| /* Construct the new shape. */ |
| RootedId id(cx); |
| RootedValue undefinedValue(cx, UndefinedValue()); |
| for (unsigned i = 0; i < ids.length(); i++) { |
| id = ids[i]; |
| if (!DefineNativeProperty(cx, res, id, undefinedValue, NULL, NULL, |
| JSPROP_ENUMERATE, 0, 0, DNP_SKIP_TYPE)) { |
| return NULL; |
| } |
| } |
| JS_ASSERT(!res->inDictionaryMode()); |
| |
| return res; |
| } |
| |
| JSObject* |
| js::CreateThis(JSContext *cx, Class *newclasp, HandleObject callee) |
| { |
| RootedValue protov(cx); |
| if (!JSObject::getProperty(cx, callee, callee, cx->names().classPrototype, &protov)) |
| return NULL; |
| |
| JSObject *proto = protov.isObjectOrNull() ? protov.toObjectOrNull() : NULL; |
| JSObject *parent = callee->getParent(); |
| gc::AllocKind kind = NewObjectGCKind(newclasp); |
| return NewObjectWithClassProto(cx, newclasp, proto, parent, kind); |
| } |
| |
| static inline JSObject * |
| CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, |
| NewObjectKind newKind) |
| { |
| if (type->newScript) { |
| /* |
| * Make an object with the type's associated finalize kind and shape, |
| * which reflects any properties that will definitely be added to the |
| * object before it is read from. |
| */ |
| gc::AllocKind kind = type->newScript->allocKind; |
| RootedObject res(cx, NewObjectWithType(cx, type, parent, kind, newKind)); |
| if (!res) |
| return NULL; |
| RootedObject metadata(cx, res->getMetadata()); |
| RootedShape shape(cx, type->newScript->shape); |
| JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, res, shape)); |
| if (metadata && !JSObject::setMetadata(cx, res, metadata)) |
| return NULL; |
| return res; |
| } |
| |
| gc::AllocKind allocKind = NewObjectGCKind(&ObjectClass); |
| return NewObjectWithType(cx, type, parent, allocKind, newKind); |
| } |
| |
| JSObject * |
| js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject *proto, |
| NewObjectKind newKind /* = GenericObject */) |
| { |
| JSObject *res; |
| |
| if (proto) { |
| RootedTypeObject type(cx, proto->getNewType(cx, &ObjectClass, &callee->as<JSFunction>())); |
| if (!type) |
| return NULL; |
| res = CreateThisForFunctionWithType(cx, type, callee->getParent(), newKind); |
| } else { |
| gc::AllocKind allocKind = NewObjectGCKind(&ObjectClass); |
| res = NewObjectWithClassProto(cx, &ObjectClass, proto, callee->getParent(), allocKind, newKind); |
| } |
| |
| if (res && cx->typeInferenceEnabled()) { |
| JSScript *script = callee->as<JSFunction>().nonLazyScript(); |
| TypeScript::SetThis(cx, script, types::Type::ObjectType(res)); |
| } |
| |
| return res; |
| } |
| |
| JSObject * |
| js::CreateThisForFunction(JSContext *cx, HandleObject callee, bool newType) |
| { |
| RootedValue protov(cx); |
| if (!JSObject::getProperty(cx, callee, callee, cx->names().classPrototype, &protov)) |
| return NULL; |
| JSObject *proto; |
| if (protov.isObject()) |
| proto = &protov.toObject(); |
| else |
| proto = NULL; |
| NewObjectKind newKind = newType ? SingletonObject : GenericObject; |
| JSObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); |
| |
| if (obj && newType) { |
| RootedObject nobj(cx, obj); |
| |
| /* Reshape the singleton before passing it as the 'this' value. */ |
| JSObject::clear(cx, nobj); |
| |
| JSScript *calleeScript = callee->as<JSFunction>().nonLazyScript(); |
| TypeScript::SetThis(cx, calleeScript, types::Type::ObjectType(nobj)); |
| |
| return nobj; |
| } |
| |
| return obj; |
| } |
| |
| /* |
| * Given pc pointing after a property accessing bytecode, return true if the |
| * access is "object-detecting" in the sense used by web scripts, e.g., when |
| * checking whether document.all is defined. |
| */ |
| static bool |
| Detecting(JSContext *cx, JSScript *script, jsbytecode *pc) |
| { |
| /* General case: a branch or equality op follows the access. */ |
| JSOp op = JSOp(*pc); |
| if (js_CodeSpec[op].format & JOF_DETECTING) |
| return true; |
| |
| jsbytecode *endpc = script->code + script->length; |
| JS_ASSERT(script->code <= pc && pc < endpc); |
| |
| if (op == JSOP_NULL) { |
| /* |
| * Special case #1: handle (document.all == null). Don't sweat |
| * about JS1.2's revision of the equality operators here. |
| */ |
| if (++pc < endpc) { |
| op = JSOp(*pc); |
| return op == JSOP_EQ || op == JSOP_NE; |
| } |
| return false; |
| } |
| |
| if (op == JSOP_GETGNAME || op == JSOP_NAME) { |
| /* |
| * Special case #2: handle (document.all == undefined). Don't worry |
| * about a local variable named |undefined| shadowing the immutable |
| * global binding...because, really? |
| */ |
| JSAtom *atom = script->getAtom(GET_UINT32_INDEX(pc)); |
| if (atom == cx->names().undefined && |
| (pc += js_CodeSpec[op].length) < endpc) { |
| op = JSOp(*pc); |
| return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| * Infer lookup flags from the currently executing bytecode, returning |
| * defaultFlags if a currently executing bytecode cannot be determined. |
| */ |
| unsigned |
| js_InferFlags(JSContext *cx, unsigned defaultFlags) |
| { |
| /* |
| * We intentionally want to look across compartment boundaries to correctly |
| * handle the case of cross-compartment property access. |
| */ |
| jsbytecode *pc; |
| JSScript *script = cx->currentScript(&pc, JSContext::ALLOW_CROSS_COMPARTMENT); |
| if (!script) |
| return defaultFlags; |
| |
| uint32_t format = js_CodeSpec[*pc].format; |
| unsigned flags = 0; |
| if (format & JOF_SET) |
| flags |= JSRESOLVE_ASSIGNING; |
| return flags; |
| } |
| |
| /* static */ JSBool |
| JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, |
| HandleId id, MutableHandleValue vp, JSBool strict) |
| { |
| if (JS_UNLIKELY(obj->watched())) { |
| WatchpointMap *wpmap = cx->compartment()->watchpointMap; |
| if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) |
| return false; |
| } |
| return obj->getOps()->setGeneric(cx, obj, id, vp, strict); |
| } |
| |
| /* static */ JSBool |
| JSObject::nonNativeSetElement(JSContext *cx, HandleObject obj, |
| uint32_t index, MutableHandleValue vp, JSBool strict) |
| { |
| if (JS_UNLIKELY(obj->watched())) { |
| RootedId id(cx); |
| if (!IndexToId(cx, index, &id)) |
| return false; |
| |
| WatchpointMap *wpmap = cx->compartment()->watchpointMap; |
| if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) |
| return false; |
| } |
| return obj->getOps()->setElement(cx, obj, index, vp, strict); |
| } |
| |
| /* static */ bool |
| JSObject::deleteByValue(JSContext *cx, HandleObject obj, const Value &property, JSBool *succeeded) |
| { |
| uint32_t index; |
| if (IsDefinitelyIndex(property, &index)) |
| return deleteElement(cx, obj, index, succeeded); |
| |
| RootedValue propval(cx, property); |
| Rooted<SpecialId> sid(cx); |
| if (ValueIsSpecial(obj, &propval, &sid, cx)) |
| return deleteSpecial(cx, obj, sid, succeeded); |
| |
| JSAtom *name = ToAtom<CanGC>(cx, propval); |
| if (!name) |
| return false; |
| |
| if (name->isIndex(&index)) |
| return deleteElement(cx, obj, index, succeeded); |
| |
| Rooted<PropertyName*> propname(cx, name->asPropertyName()); |
| return deleteProperty(cx, obj, propname, succeeded); |
| } |
| |
| JS_FRIEND_API(bool) |
| JS_CopyPropertiesFrom(JSContext *cx, JSObject *targetArg, JSObject *objArg) |
| { |
| RootedObject target(cx, targetArg); |
| RootedObject obj(cx, objArg); |
| |
| // If we're not native, then we cannot copy properties. |
| JS_ASSERT(target->isNative() == obj->isNative()); |
| if (!target->isNative()) |
| return true; |
| |
| AutoShapeVector shapes(cx); |
| for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) { |
| if (!shapes.append(&r.front())) |
| return false; |
| } |
| |
| RootedShape shape(cx); |
| RootedValue v(cx); |
| RootedId id(cx); |
| size_t n = shapes.length(); |
| while (n > 0) { |
| shape = shapes[--n]; |
| unsigned attrs = shape->attributes(); |
| PropertyOp getter = shape->getter(); |
| StrictPropertyOp setter = shape->setter(); |
| AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
| if ((attrs & JSPROP_GETTER) && !cx->compartment()->wrap(cx, &getter)) |
| return false; |
| if ((attrs & JSPROP_SETTER) && !cx->compartment()->wrap(cx, &setter)) |
| return false; |
| v = shape->hasSlot() ? obj->getSlot(shape->slot()) : UndefinedValue(); |
| if (!cx->compartment()->wrap(cx, &v)) |
| return false; |
| id = shape->propid(); |
| if (!JSObject::defineGeneric(cx, target, id, v, getter, setter, attrs)) |
| return false; |
| } |
| return true; |
| } |
| |
| static bool |
| CopySlots(JSContext *cx, HandleObject from, HandleObject to) |
| { |
| JS_ASSERT(!from->isNative() && !to->isNative()); |
| JS_ASSERT(from->getClass() == to->getClass()); |
| |
| size_t n = 0; |
| if (from->isWrapper() && |
| (Wrapper::wrapperHandler(from)->flags() & |
| Wrapper::CROSS_COMPARTMENT)) { |
| to->setSlot(0, from->getSlot(0)); |
| to->setSlot(1, from->getSlot(1)); |
| n = 2; |
| } |
| |
| size_t span = JSCLASS_RESERVED_SLOTS(from->getClass()); |
| RootedValue v(cx); |
| for (; n < span; ++n) { |
| v = from->getSlot(n); |
| if (!cx->compartment()->wrap(cx, &v)) |
| return false; |
| to->setSlot(n, v); |
| } |
| return true; |
| } |
| |
| JSObject * |
| js::CloneObject(JSContext *cx, HandleObject obj, Handle<js::TaggedProto> proto, HandleObject parent) |
| { |
| if (!obj->isNative() && !obj->isProxy()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_CANT_CLONE_OBJECT); |
| return NULL; |
| } |
| RootedObject clone(cx, NewObjectWithGivenProto(cx, obj->getClass(), proto, parent)); |
| if (!clone) |
| return NULL; |
| if (obj->isNative()) { |
| if (clone->is<JSFunction>() && (obj->compartment() != clone->compartment())) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_CANT_CLONE_OBJECT); |
| return NULL; |
| } |
| |
| if (obj->hasPrivate()) |
| clone->setPrivate(obj->getPrivate()); |
| } else { |
| JS_ASSERT(obj->isProxy()); |
| if (!CopySlots(cx, obj, clone)) |
| return NULL; |
| } |
| |
| return clone; |
| } |
| |
| JSObject * |
| js::CloneObjectLiteral(JSContext *cx, HandleObject parent, HandleObject srcObj) |
| { |
| Rooted<TypeObject*> typeObj(cx); |
| typeObj = cx->global()->getOrCreateObjectPrototype(cx)->getNewType(cx, &ObjectClass); |
| |
| JS_ASSERT(srcObj->getClass() == &ObjectClass); |
| AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots())); |
| JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind()); |
| |
| RootedShape shape(cx, srcObj->lastProperty()); |
| return NewReshapedObject(cx, typeObj, parent, kind, shape); |
| } |
| |
| struct JSObject::TradeGutsReserved { |
| Vector<Value> avals; |
| Vector<Value> bvals; |
| int newafixed; |
| int newbfixed; |
| RootedShape newashape; |
| RootedShape newbshape; |
| HeapSlot *newaslots; |
| HeapSlot *newbslots; |
| |
| TradeGutsReserved(JSContext *cx) |
| : avals(cx), bvals(cx), |
| newafixed(0), newbfixed(0), |
| newashape(cx), newbshape(cx), |
| newaslots(NULL), newbslots(NULL) |
| {} |
| |
| ~TradeGutsReserved() |
| { |
| if (newaslots) |
| js_free(newaslots); |
| if (newbslots) |
| js_free(newbslots); |
| } |
| }; |
| |
| bool |
| JSObject::ReserveForTradeGuts(JSContext *cx, JSObject *aArg, JSObject *bArg, |
| TradeGutsReserved &reserved) |
| { |
| /* |
| * Avoid GC in here to avoid confusing the tracing code with our |
| * intermediate state. |
| */ |
| AutoSuppressGC suppress(cx); |
| |
| RootedObject a(cx, aArg); |
| RootedObject b(cx, bArg); |
| JS_ASSERT(a->compartment() == b->compartment()); |
| AutoCompartment ac(cx, a); |
| |
| /* |
| * When performing multiple swaps between objects which may have different |
| * numbers of fixed slots, we reserve all space ahead of time so that the |
| * swaps can be performed infallibly. |
| */ |
| |
| /* |
| * Swap prototypes and classes on the two objects, so that TradeGuts can |
| * preserve the types of the two objects. |
| */ |
| Class *aClass = a->getClass(); |
| Class *bClass = b->getClass(); |
| Rooted<TaggedProto> aProto(cx, a->getTaggedProto()); |
| Rooted<TaggedProto> bProto(cx, b->getTaggedProto()); |
| if (!SetClassAndProto(cx, a, bClass, bProto, false)) |
| return false; |
| if (!SetClassAndProto(cx, b, aClass, aProto, false)) |
| return false; |
| |
| if (a->tenuredSizeOfThis() == b->tenuredSizeOfThis()) |
| return true; |
| |
| /* |
| * If either object is native, it needs a new shape to preserve the |
| * invariant that objects with the same shape have the same number of |
| * inline slots. The fixed slots will be updated in place during TradeGuts. |
| * Non-native objects need to be reshaped according to the new count. |
| */ |
| if (a->isNative()) { |
| if (!a->generateOwnShape(cx)) |
| return false; |
| } else { |
| reserved.newbshape = EmptyShape::getInitialShape(cx, aClass, aProto, a->getParent(), a->getMetadata(), |
| b->tenuredGetAllocKind()); |
| if (!reserved.newbshape) |
| return false; |
| } |
| if (b->isNative()) { |
| if (!b->generateOwnShape(cx)) |
| return false; |
| } else { |
| reserved.newashape = EmptyShape::getInitialShape(cx, bClass, bProto, b->getParent(), b->getMetadata(), |
| a->tenuredGetAllocKind()); |
| if (!reserved.newashape) |
| return false; |
| } |
| |
| /* The avals/bvals vectors hold all original values from the objects. */ |
| |
| if (!reserved.avals.reserve(a->slotSpan())) |
| return false; |
| if (!reserved.bvals.reserve(b->slotSpan())) |
| return false; |
| |
| /* |
| * The newafixed/newbfixed hold the number of fixed slots in the objects |
| * after the swap. Adjust these counts according to whether the objects |
| * use their last fixed slot for storing private data. |
| */ |
| |
| reserved.newafixed = a->numFixedSlots(); |
| reserved.newbfixed = b->numFixedSlots(); |
| |
| if (aClass->hasPrivate()) { |
| reserved.newafixed++; |
| reserved.newbfixed--; |
| } |
| if (bClass->hasPrivate()) { |
| reserved.newbfixed++; |
| reserved.newafixed--; |
| } |
| |
| JS_ASSERT(reserved.newafixed >= 0); |
| JS_ASSERT(reserved.newbfixed >= 0); |
| |
| /* |
| * The newaslots/newbslots arrays hold any dynamic slots for the objects |
| * if they do not have enough fixed slots to accomodate the slots in the |
| * other object. |
| */ |
| |
| unsigned adynamic = dynamicSlotsCount(reserved.newafixed, b->slotSpan()); |
| unsigned bdynamic = dynamicSlotsCount(reserved.newbfixed, a->slotSpan()); |
| |
| if (adynamic) { |
| reserved.newaslots = cx->pod_malloc<HeapSlot>(adynamic); |
| if (!reserved.newaslots) |
| return false; |
| Debug_SetSlotRangeToCrashOnTouch(reserved.newaslots, adynamic); |
| } |
| if (bdynamic) { |
| reserved.newbslots = cx->pod_malloc<HeapSlot>(bdynamic); |
| if (!reserved.newbslots) |
| return false; |
| Debug_SetSlotRangeToCrashOnTouch(reserved.newbslots, bdynamic); |
| } |
| |
| return true; |
| } |
| |
| void |
| JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved &reserved) |
| { |
| JS_ASSERT(a->compartment() == b->compartment()); |
| JS_ASSERT(a->is<JSFunction>() == b->is<JSFunction>()); |
| |
| /* |
| * Swap the object's types, to restore their initial type information. |
| * The prototypes and classes of the objects were swapped in ReserveForTradeGuts. |
| */ |
| TypeObject *tmp = a->type_; |
| a->type_ = b->type_; |
| b->type_ = tmp; |
| |
| /* Don't try to swap a JSFunction for a plain function JSObject. */ |
| JS_ASSERT_IF(a->is<JSFunction>(), a->tenuredSizeOfThis() == b->tenuredSizeOfThis()); |
| |
| /* |
| * Regexp guts are more complicated -- we would need to migrate the |
| * refcounted JIT code blob for them across compartments instead of just |
| * swapping guts. |
| */ |
| JS_ASSERT(!a->is<RegExpObject>() && !b->is<RegExpObject>()); |
| |
| /* Arrays can use their fixed storage for elements. */ |
| JS_ASSERT(!a->isArray() && !b->isArray()); |
| |
| /* |
| * Callers should not try to swap ArrayBuffer objects, |
| * these use a different slot representation from other objects. |
| */ |
| JS_ASSERT(!a->is<ArrayBufferObject>() && !b->is<ArrayBufferObject>()); |
| |
| /* Trade the guts of the objects. */ |
| const size_t size = a->tenuredSizeOfThis(); |
| if (size == b->tenuredSizeOfThis()) { |
| /* |
| * If the objects are the same size, then we make no assumptions about |
| * whether they have dynamically allocated slots and instead just copy |
| * them over wholesale. |
| */ |
| char tmp[tl::Max<sizeof(JSFunction), sizeof(JSObject_Slots16)>::result]; |
| JS_ASSERT(size <= sizeof(tmp)); |
| |
| js_memcpy(tmp, a, size); |
| js_memcpy(a, b, size); |
| js_memcpy(b, tmp, size); |
| |
| #ifdef JSGC_GENERATIONAL |
| /* |
| * Trigger post barriers for fixed slots. JSObject bits are barriered |
| * below, in common with the other case. |
| */ |
| for (size_t i = 0; i < a->numFixedSlots(); ++i) { |
| HeapSlot::writeBarrierPost(cx->runtime(), a, HeapSlot::Slot, i); |
| HeapSlot::writeBarrierPost(cx->runtime(), b, HeapSlot::Slot, i); |
| } |
| #endif |
| } else { |
| /* |
| * If the objects are of differing sizes, use the space we reserved |
| * earlier to save the slots from each object and then copy them into |
| * the new layout for the other object. |
| */ |
| |
| uint32_t acap = a->slotSpan(); |
| uint32_t bcap = b->slotSpan(); |
| |
| for (size_t i = 0; i < acap; i++) |
| reserved.avals.infallibleAppend(a->getSlot(i)); |
| |
| for (size_t i = 0; i < bcap; i++) |
| reserved.bvals.infallibleAppend(b->getSlot(i)); |
| |
| /* Done with the dynamic slots. */ |
| if (a->hasDynamicSlots()) |
| js_free(a->slots); |
| if (b->hasDynamicSlots()) |
| js_free(b->slots); |
| |
| void *apriv = a->hasPrivate() ? a->getPrivate() : NULL; |
| void *bpriv = b->hasPrivate() ? b->getPrivate() : NULL; |
| |
| char tmp[sizeof(JSObject)]; |
| js_memcpy(&tmp, a, sizeof tmp); |
| js_memcpy(a, b, sizeof tmp); |
| js_memcpy(b, &tmp, sizeof tmp); |
| |
| if (a->isNative()) |
| a->shape_->setNumFixedSlots(reserved.newafixed); |
| else |
| a->shape_ = reserved.newashape; |
| |
| a->slots = reserved.newaslots; |
| a->initSlotRange(0, reserved.bvals.begin(), bcap); |
| if (a->hasPrivate()) |
| a->initPrivate(bpriv); |
| |
| if (b->isNative()) |
| b->shape_->setNumFixedSlots(reserved.newbfixed); |
| else |
| b->shape_ = reserved.newbshape; |
| |
| b->slots = reserved.newbslots; |
| b->initSlotRange(0, reserved.avals.begin(), acap); |
| if (b->hasPrivate()) |
| b->initPrivate(apriv); |
| |
| /* Make sure the destructor for reserved doesn't free the slots. */ |
| reserved.newaslots = NULL; |
| reserved.newbslots = NULL; |
| } |
| |
| #ifdef JSGC_GENERATIONAL |
| Shape::writeBarrierPost(a->shape_, &a->shape_); |
| Shape::writeBarrierPost(b->shape_, &b->shape_); |
| types::TypeObject::writeBarrierPost(a->type_, &a->type_); |
| types::TypeObject::writeBarrierPost(b->type_, &b->type_); |
| #endif |
| |
| if (a->inDictionaryMode()) |
| a->lastProperty()->listp = &a->shape_; |
| if (b->inDictionaryMode()) |
| b->lastProperty()->listp = &b->shape_; |
| |
| #ifdef JSGC_INCREMENTAL |
| /* |
| * We need a write barrier here. If |a| was marked and |b| was not, then |
| * after the swap, |b|'s guts would never be marked. The write barrier |
| * solves this. |
| * |
| * Normally write barriers happen before the write. However, that's not |
| * necessary here because nothing is being destroyed. We're just swapping. |
| * We don't do the barrier before TradeGuts because ReserveForTradeGuts |
| * makes changes to the objects that might confuse the tracing code. |
| */ |
| JS::Zone *zone = a->zone(); |
| if (zone->needsBarrier()) { |
| MarkChildren(zone->barrierTracer(), a); |
| MarkChildren(zone->barrierTracer(), b); |
| } |
| #endif |
| } |
| |
| /* Use this method with extreme caution. It trades the guts of two objects. */ |
| bool |
| JSObject::swap(JSContext *cx, HandleObject a, HandleObject b) |
| { |
| AutoMarkInDeadZone adc1(a->zone()); |
| AutoMarkInDeadZone adc2(b->zone()); |
| |
| // Ensure swap doesn't cause a finalizer to not be run. |
| JS_ASSERT(IsBackgroundFinalized(a->tenuredGetAllocKind()) == |
| IsBackgroundFinalized(b->tenuredGetAllocKind())); |
| JS_ASSERT(a->compartment() == b->compartment()); |
| |
| unsigned r = NotifyGCPreSwap(a, b); |
| |
| TradeGutsReserved reserved(cx); |
| if (!ReserveForTradeGuts(cx, a, b, reserved)) { |
| NotifyGCPostSwap(b, a, r); |
| return false; |
| } |
| TradeGuts(cx, a, b, reserved); |
| |
| NotifyGCPostSwap(a, b, r); |
| return true; |
| } |
| |
| static bool |
| DefineStandardSlot(JSContext *cx, HandleObject obj, JSProtoKey key, JSAtom *atom, |
| HandleValue v, uint32_t attrs, bool &named) |
| { |
| RootedId id(cx, AtomToId(atom)); |
| |
| if (key != JSProto_Null) { |
| /* |
| * Initializing an actual standard class on a global object. If the |
| * property is not yet present, force it into a new one bound to a |
| * reserved slot. Otherwise, go through the normal property path. |
| */ |
| JS_ASSERT(obj->is<GlobalObject>()); |
| JS_ASSERT(obj->isNative()); |
| |
| if (!obj->nativeLookup(cx, id)) { |
| uint32_t slot = 2 * JSProto_LIMIT + key; |
| obj->setReservedSlot(slot, v); |
| if (!JSObject::addProperty(cx, obj, id, JS_PropertyStub, JS_StrictPropertyStub, slot, attrs, 0, 0)) |
| return false; |
| AddTypePropertyId(cx, obj, id, v); |
| |
| named = true; |
| return true; |
| } |
| } |
| |
| named = JSObject::defineGeneric(cx, obj, id, |
| v, JS_PropertyStub, JS_StrictPropertyStub, attrs); |
| return named; |
| } |
| |
| static void |
| SetClassObject(JSObject *obj, JSProtoKey key, JSObject *cobj, JSObject *proto) |
| { |
| JS_ASSERT(!obj->getParent()); |
| if (!obj->is<GlobalObject>()) |
| return; |
| |
| obj->setReservedSlot(key, ObjectOrNullValue(cobj)); |
| obj->setReservedSlot(JSProto_LIMIT + key, ObjectOrNullValue(proto)); |
| } |
| |
| static void |
| ClearClassObject(JSObject *obj, JSProtoKey key) |
| { |
| JS_ASSERT(!obj->getParent()); |
| if (!obj->is<GlobalObject>()) |
| return; |
| |
| obj->setSlot(key, UndefinedValue()); |
| obj->setSlot(JSProto_LIMIT + key, UndefinedValue()); |
| } |
| |
| JSObject * |
| js::DefineConstructorAndPrototype(JSContext *cx, HandleObject obj, JSProtoKey key, HandleAtom atom, |
| JSObject *protoProto, Class *clasp, |
| Native constructor, unsigned nargs, |
| const JSPropertySpec *ps, const JSFunctionSpec *fs, |
| const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, |
| JSObject **ctorp, AllocKind ctorKind) |
| { |
| /* |
| * Create a prototype object for this class. |
| * |
| * FIXME: lazy standard (built-in) class initialization and even older |
| * eager boostrapping code rely on all of these properties: |
| * |
| * 1. NewObject attempting to compute a default prototype object when |
| * passed null for proto; and |
| * |
| * 2. NewObject tolerating no default prototype (null proto slot value) |
| * due to this js_InitClass call coming from js_InitFunctionClass on an |
| * otherwise-uninitialized global. |
| * |
| * 3. NewObject allocating a JSFunction-sized GC-thing when clasp is |
| * &JSFunction::class_, not a JSObject-sized (smaller) GC-thing. |
| * |
| * The JS_NewObjectForGivenProto and JS_NewObject APIs also allow clasp to |
| * be &JSFunction::class_ (we could break compatibility easily). But |
| * fixing (3) is not enough without addressing the bootstrapping dependency |
| * on (1) and (2). |
| */ |
| |
| /* |
| * Create the prototype object. (GlobalObject::createBlankPrototype isn't |
| * used because it parents the prototype object to the global and because |
| * it uses WithProto::Given. FIXME: Undo dependencies on this parentage |
| * [which already needs to happen for bug 638316], figure out nicer |
| * semantics for null-protoProto, and use createBlankPrototype.) |
| */ |
| RootedObject proto(cx, NewObjectWithClassProto(cx, clasp, protoProto, obj, SingletonObject)); |
| if (!proto) |
| return NULL; |
| |
| /* After this point, control must exit via label bad or out. */ |
| RootedObject ctor(cx); |
| bool named = false; |
| bool cached = false; |
| if (!constructor) { |
| /* |
| * Lacking a constructor, name the prototype (e.g., Math) unless this |
| * class (a) is anonymous, i.e. for internal use only; (b) the class |
| * of obj (the global object) is has a reserved slot indexed by key; |
| * and (c) key is not the null key. |
| */ |
| if (!(clasp->flags & JSCLASS_IS_ANONYMOUS) || !obj->is<GlobalObject>() || |
| key == JSProto_Null) |
| { |
| uint32_t attrs = (clasp->flags & JSCLASS_IS_ANONYMOUS) |
| ? JSPROP_READONLY | JSPROP_PERMANENT |
| : 0; |
| RootedValue value(cx, ObjectValue(*proto)); |
| if (!DefineStandardSlot(cx, obj, key, atom, value, attrs, named)) |
| goto bad; |
| } |
| |
| ctor = proto; |
| } else { |
| /* |
| * Create the constructor, not using GlobalObject::createConstructor |
| * because the constructor currently must have |obj| as its parent. |
| * (FIXME: remove this dependency on the exact identity of the parent, |
| * perhaps as part of bug 638316.) |
| */ |
| RootedFunction fun(cx, NewFunction(cx, NullPtr(), constructor, nargs, |
| JSFunction::NATIVE_CTOR, obj, atom, ctorKind)); |
| if (!fun) |
| goto bad; |
| |
| /* |
| * Set the class object early for standard class constructors. Type |
| * inference may need to access these, and js_GetClassPrototype will |
| * fail if it tries to do a reentrant reconstruction of the class. |
| */ |
| if (key != JSProto_Null) { |
| SetClassObject(obj, key, fun, proto); |
| cached = true; |
| } |
| |
| RootedValue value(cx, ObjectValue(*fun)); |
| if (!DefineStandardSlot(cx, obj, key, atom, value, 0, named)) |
| goto bad; |
| |
| /* |
| * Optionally construct the prototype object, before the class has |
| * been fully initialized. Allow the ctor to replace proto with a |
| * different object, as is done for operator new. |
| */ |
| ctor = fun; |
| if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
| goto bad; |
| |
| /* Bootstrap Function.prototype (see also JS_InitStandardClasses). */ |
| Rooted<TaggedProto> tagged(cx, TaggedProto(proto)); |
| if (ctor->getClass() == clasp && !ctor->splicePrototype(cx, clasp, tagged)) |
| goto bad; |
| } |
| |
| if (!DefinePropertiesAndBrand(cx, proto, ps, fs) || |
| (ctor != proto && !DefinePropertiesAndBrand(cx, ctor, static_ps, static_fs))) |
| { |
| goto bad; |
| } |
| |
| if (clasp->flags & (JSCLASS_FREEZE_PROTO|JSCLASS_FREEZE_CTOR)) { |
| JS_ASSERT_IF(ctor == proto, !(clasp->flags & JSCLASS_FREEZE_CTOR)); |
| if (proto && (clasp->flags & JSCLASS_FREEZE_PROTO) && !JSObject::freeze(cx, proto)) |
| goto bad; |
| if (ctor && (clasp->flags & JSCLASS_FREEZE_CTOR) && !JSObject::freeze(cx, ctor)) |
| goto bad; |
| } |
| |
| /* If this is a standard class, cache its prototype. */ |
| if (!cached && key != JSProto_Null) |
| SetClassObject(obj, key, ctor, proto); |
| |
| if (ctorp) |
| *ctorp = ctor; |
| return proto; |
| |
| bad: |
| if (named) { |
| JSBool succeeded; |
| JSObject::deleteByValue(cx, obj, StringValue(atom), &succeeded); |
| } |
| if (cached) |
| ClearClassObject(obj, key); |
| return NULL; |
| } |
| |
| /* |
| * Lazy standard classes need a way to indicate if they have been initialized. |
| * Otherwise, when we delete them, we might accidentally recreate them via a |
| * lazy initialization. We use the presence of a ctor or proto in the |
| * global object's slot to indicate that they've been constructed, but this only |
| * works for classes which have a proto and ctor. Classes which don't have one |
| * can call MarkStandardClassInitializedNoProto(), and we can always check |
| * whether a class is initialized by calling IsStandardClassResolved(). |
| */ |
| bool |
| js::IsStandardClassResolved(JSObject *obj, js::Class *clasp) |
| { |
| JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp); |
| |
| /* If the constructor is undefined, then it hasn't been initialized. */ |
| return (obj->getReservedSlot(key) != UndefinedValue()); |
| } |
| |
| void |
| js::MarkStandardClassInitializedNoProto(JSObject *obj, js::Class *clasp) |
| { |
| JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp); |
| |
| /* |
| * We use True so that it's obvious what we're doing (instead of, say, |
| * Null, which might be miscontrued as an error in setting Undefined). |
| */ |
| if (obj->getReservedSlot(key) == UndefinedValue()) |
| obj->setSlot(key, BooleanValue(true)); |
| } |
| |
| JSObject * |
| js_InitClass(JSContext *cx, HandleObject obj, JSObject *protoProto_, |
| Class *clasp, Native constructor, unsigned nargs, |
| const JSPropertySpec *ps, const JSFunctionSpec *fs, |
| const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, |
| JSObject **ctorp, AllocKind ctorKind) |
| { |
| RootedObject protoProto(cx, protoProto_); |
| |
| RootedAtom atom(cx, Atomize(cx, clasp->name, strlen(clasp->name))); |
| if (!atom) |
| return NULL; |
| |
| /* |
| * All instances of the class will inherit properties from the prototype |
| * object we are about to create (in DefineConstructorAndPrototype), which |
| * in turn will inherit from protoProto. |
| * |
| * When initializing a standard class (other than Object), if protoProto is |
| * null, default to the Object prototype object. The engine's internal uses |
| * of js_InitClass depend on this nicety. Note that in |
| * js_InitFunctionAndObjectClasses, we specially hack the resolving table |
| * and then depend on js_GetClassPrototype here leaving protoProto NULL and |
| * returning true. |
| */ |
| JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp); |
| if (key != JSProto_Null && |
| !protoProto && |
| !js_GetClassPrototype(cx, JSProto_Object, &protoProto)) { |
| return NULL; |
| } |
| |
| return DefineConstructorAndPrototype(cx, obj, key, atom, protoProto, clasp, constructor, nargs, |
| ps, fs, static_ps, static_fs, ctorp, ctorKind); |
| } |
| |
| /* static */ inline bool |
| JSObject::updateSlotsForSpan(JSContext *cx, HandleObject obj, size_t oldSpan, size_t newSpan) |
| { |
| JS_ASSERT(oldSpan != newSpan); |
| |
| size_t oldCount = dynamicSlotsCount(obj->numFixedSlots(), oldSpan); |
| size_t newCount = dynamicSlotsCount(obj->numFixedSlots(), newSpan); |
| |
| if (oldSpan < newSpan) { |
| if (oldCount < newCount && !JSObject::growSlots(cx, obj, oldCount, newCount)) |
| return false; |
| |
| if (newSpan == oldSpan + 1) |
| obj->initSlotUnchecked(oldSpan, UndefinedValue()); |
| else |
| obj->initializeSlotRange(oldSpan, newSpan - oldSpan); |
| } else { |
| /* Trigger write barriers on the old slots before reallocating. */ |
| obj->prepareSlotRangeForOverwrite(newSpan, oldSpan); |
| obj->invalidateSlotRange(newSpan, oldSpan - newSpan); |
| |
| if (oldCount > newCount) |
| JSObject::shrinkSlots(cx, obj, oldCount, newCount); |
| } |
| |
| return true; |
| } |
| |
| /* static */ bool |
| JSObject::setLastProperty(JSContext *cx, HandleObject obj, HandleShape shape) |
| { |
| JS_ASSERT(!obj->inDictionaryMode()); |
| JS_ASSERT(!shape->inDictionary()); |
| JS_ASSERT(shape->compartment() == obj->compartment()); |
| JS_ASSERT(shape->numFixedSlots() == obj->numFixedSlots()); |
| |
| size_t oldSpan = obj->lastProperty()->slotSpan(); |
| size_t newSpan = shape->slotSpan(); |
| |
| if (oldSpan == newSpan) { |
| obj->shape_ = shape; |
| return true; |
| } |
| |
| if (!updateSlotsForSpan(cx, obj, oldSpan, newSpan)) |
| return false; |
| |
| obj->shape_ = shape; |
| return true; |
| } |
| |
| /* static */ bool |
| JSObject::setSlotSpan(JSContext *cx, HandleObject obj, uint32_t span) |
| { |
| JS_ASSERT(obj->inDictionaryMode()); |
| |
| size_t oldSpan = obj->lastProperty()->base()->slotSpan(); |
| if (oldSpan == span) |
| return true; |
| |
| if (!JSObject::updateSlotsForSpan(cx, obj, oldSpan, span)) |
| return false; |
| |
| obj->lastProperty()->base()->setSlotSpan(span); |
| return true; |
| } |
| |
| static HeapSlot * |
| AllocateSlots(JSContext *cx, JSObject *obj, uint32_t nslots) |
| { |
| #ifdef JSGC_GENERATIONAL |
| return cx->runtime()->gcNursery.allocateSlots(cx, obj, nslots); |
| #else |
| return cx->pod_malloc<HeapSlot>(nslots); |
| #endif |
| } |
| |
| static HeapSlot * |
| ReallocateSlots(JSContext *cx, JSObject *obj, HeapSlot *oldSlots, |
| uint32_t oldCount, uint32_t newCount) |
| { |
| #ifdef JSGC_GENERATIONAL |
| return cx->runtime()->gcNursery.reallocateSlots(cx, obj, oldSlots, oldCount, newCount); |
| #else |
| return (HeapSlot *)cx->realloc_(oldSlots, oldCount * sizeof(HeapSlot), |
| newCount * sizeof(HeapSlot)); |
| #endif |
| } |
| |
| /* static */ bool |
| JSObject::growSlots(JSContext *cx, HandleObject obj, uint32_t oldCount, uint32_t newCount) |
| { |
| JS_ASSERT(newCount > oldCount); |
| JS_ASSERT(newCount >= SLOT_CAPACITY_MIN); |
| |
| /* |
| * Slot capacities are determined by the span of allocated objects. Due to |
| * the limited number of bits to store shape slots, object growth is |
| * throttled well before the slot capacity can overflow. |
| */ |
| JS_ASSERT(newCount < NELEMENTS_LIMIT); |
| |
| /* |
| * If we are allocating slots for an object whose type is always created |
| * by calling 'new' on a particular script, bump the GC kind for that |
| * type to give these objects a larger number of fixed slots when future |
| * objects are constructed. |
| */ |
| if (!obj->hasLazyType() && !oldCount && obj->type()->newScript) { |
| gc::AllocKind kind = obj->type()->newScript->allocKind; |
| uint32_t newScriptSlots = gc::GetGCKindSlots(kind); |
| if (newScriptSlots == obj->numFixedSlots() && gc::TryIncrementAllocKind(&kind)) { |
| AutoEnterAnalysis enter(cx); |
| |
| Rooted<TypeObject*> typeObj(cx, obj->type()); |
| RootedShape shape(cx, typeObj->newScript->shape); |
| JSObject *reshapedObj = NewReshapedObject(cx, typeObj, obj->getParent(), kind, shape); |
| if (!reshapedObj) |
| return false; |
| |
| typeObj->newScript->allocKind = kind; |
| typeObj->newScript->shape = reshapedObj->lastProperty(); |
| typeObj->markStateChange(cx); |
| } |
| } |
| |
| if (!oldCount) { |
| obj->slots = AllocateSlots(cx, obj, newCount); |
| if (!obj->slots) |
| return false; |
| Debug_SetSlotRangeToCrashOnTouch(obj->slots, newCount); |
| return true; |
| } |
| |
| HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); |
| if (!newslots) |
| return false; /* Leave slots at its old size. */ |
| |
| bool changed = obj->slots != newslots; |
| obj->slots = newslots; |
| |
| Debug_SetSlotRangeToCrashOnTouch(obj->slots + oldCount, newCount - oldCount); |
| |
| /* Changes in the slots of global objects can trigger recompilation. */ |
| if (changed && obj->is<GlobalObject>()) |
| types::MarkObjectStateChange(cx, obj); |
| |
| return true; |
| } |
| |
| static void |
| FreeSlots(JSContext *cx, HeapSlot *slots) |
| { |
| #ifdef JSGC_GENERATIONAL |
| if (!cx->runtime()->gcNursery.isInside(slots)) |
| #endif |
| js_free(slots); |
| } |
| |
| /* static */ void |
| JSObject::shrinkSlots(JSContext *cx, HandleObject obj, uint32_t oldCount, uint32_t newCount) |
| { |
| JS_ASSERT(newCount < oldCount); |
| |
| /* |
| * Refuse to shrink slots for call objects. This only happens in a very |
| * obscure situation (deleting names introduced by a direct 'eval') and |
| * allowing the slots pointer to change may require updating pointers in |
| * the function's active args/vars information. |
| */ |
| if (obj->is<CallObject>()) |
| return; |
| |
| if (newCount == 0) { |
| FreeSlots(cx, obj->slots); |
| obj->slots = NULL; |
| return; |
| } |
| |
| JS_ASSERT(newCount >= SLOT_CAPACITY_MIN); |
| |
| HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); |
| if (!newslots) |
| return; /* Leave slots at its old size. */ |
| |
| bool changed = obj->slots != newslots; |
| obj->slots = newslots; |
| |
| /* Watch for changes in global object slots, as for growSlots. */ |
| if (changed && obj->is<GlobalObject>()) |
| types::MarkObjectStateChange(cx, obj); |
| } |
| |
| /* static */ bool |
| JSObject::sparsifyDenseElement(JSContext *cx, HandleObject obj, uint32_t index) |
| { |
| RootedValue value(cx, obj->getDenseElement(index)); |
| JS_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); |
| |
| JSObject::removeDenseElementForSparseIndex(cx, obj, index); |
| |
| uint32_t slot = obj->slotSpan(); |
| if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) { |
| obj->setDenseElement(index, value); |
| return false; |
| } |
| |
| JS_ASSERT(slot == obj->slotSpan() - 1); |
| obj->initSlot(slot, value); |
| |
| return true; |
| } |
| |
| /* static */ bool |
| JSObject::sparsifyDenseElements(JSContext *cx, HandleObject obj) |
| { |
| uint32_t initialized = obj->getDenseInitializedLength(); |
| |
| /* Create new properties with the value of non-hole dense elements. */ |
| for (uint32_t i = 0; i < initialized; i++) { |
| if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) |
| continue; |
| |
| if (!sparsifyDenseElement(cx, obj, i)) |
| return false; |
| } |
| |
| if (initialized) |
| obj->setDenseInitializedLength(0); |
| |
| /* |
| * Reduce storage for dense elements which are now holes. Explicitly mark |
| * the elements capacity as zero, so that any attempts to add dense |
| * elements will be caught in ensureDenseElements. |
| */ |
| if (obj->getDenseCapacity()) { |
| obj->shrinkElements(cx, 0); |
| obj->getElementsHeader()->capacity = 0; |
| } |
| |
| return true; |
| } |
| |
| bool |
| JSObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint) |
| { |
| JS_ASSERT(isNative()); |
| JS_ASSERT(requiredCapacity > MIN_SPARSE_INDEX); |
| |
| uint32_t cap = getDenseCapacity(); |
| JS_ASSERT(requiredCapacity >= cap); |
| |
| if (requiredCapacity >= NELEMENTS_LIMIT) |
| return true; |
| |
| uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; |
| if (newElementsHint >= minimalDenseCount) |
| return false; |
| minimalDenseCount -= newElementsHint; |
| |
| if (minimalDenseCount > cap) |
| return true; |
| |
| uint32_t len = getDenseInitializedLength(); |
| const Value *elems = getDenseElements(); |
| for (uint32_t i = 0; i < len; i++) { |
| if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) |
| return false; |
| } |
| return true; |
| } |
| |
| /* static */ JSObject::EnsureDenseResult |
| JSObject::maybeDensifySparseElements(JSContext *cx, HandleObject obj) |
| { |
| /* |
| * Wait until after the object goes into dictionary mode, which must happen |
| * when sparsely packing any array with more than MIN_SPARSE_INDEX elements |
| * (see PropertyTree::MAX_HEIGHT). |
| */ |
| if (!obj->inDictionaryMode()) |
| return ED_SPARSE; |
| |
| /* |
| * Only measure the number of indexed properties every log(n) times when |
| * populating the object. |
| */ |
| uint32_t slotSpan = obj->slotSpan(); |
| if (slotSpan != RoundUpPow2(slotSpan)) |
| return ED_SPARSE; |
| |
| /* Watch for conditions under which an object's elements cannot be dense. */ |
| if (!obj->isExtensible() || obj->watched()) |
| return ED_SPARSE; |
| |
| /* |
| * The indexes in the object need to be sufficiently dense before they can |
| * be converted to dense mode. |
| */ |
| uint32_t numDenseElements = 0; |
| uint32_t newInitializedLength = 0; |
| |
| RootedShape shape(cx, obj->lastProperty()); |
| while (!shape->isEmptyShape()) { |
| uint32_t index; |
| if (js_IdIsIndex(shape->propid(), &index)) { |
| if (shape->attributes() == JSPROP_ENUMERATE && |
| shape->hasDefaultGetter() && |
| shape->hasDefaultSetter()) |
| { |
| numDenseElements++; |
| newInitializedLength = Max(newInitializedLength, index + 1); |
| } else { |
| /* |
| * For simplicity, only densify the object if all indexed |
| * properties can be converted to dense elements. |
| */ |
| return ED_SPARSE; |
| } |
| } |
| shape = shape->previous(); |
| } |
| |
| if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) |
| return ED_SPARSE; |
| |
| if (newInitializedLength >= NELEMENTS_LIMIT) |
| return ED_SPARSE; |
| |
| /* |
| * This object meets all necessary restrictions, convert all indexed |
| * properties into dense elements. |
| */ |
| |
| if (newInitializedLength > obj->getDenseCapacity()) { |
| if (!obj->growElements(cx, newInitializedLength)) |
| return ED_FAILED; |
| } |
| |
| obj->ensureDenseInitializedLength(cx, newInitializedLength, 0); |
| |
| RootedValue value(cx); |
| |
| shape = obj->lastProperty(); |
| while (!shape->isEmptyShape()) { |
| jsid id = shape->propid(); |
| uint32_t index; |
| if (js_IdIsIndex(id, &index)) { |
| value = obj->getSlot(shape->slot()); |
| |
| /* |
| * When removing a property from a dictionary, the specified |
| * property will be removed from the dictionary list and the |
| * last property will then be changed due to reshaping the object. |
| * Compute the next shape in the traverse, watching for such |
| * removals from the list. |
| */ |
| if (shape != obj->lastProperty()) { |
| shape = shape->previous(); |
| if (!obj->removeProperty(cx, id)) |
| return ED_FAILED; |
| } else { |
| if (!obj->removeProperty(cx, id)) |
| return ED_FAILED; |
| shape = obj->lastProperty(); |
| } |
| |
| obj->setDenseElement(index, value); |
| } else { |
| shape = shape->previous(); |
| } |
| } |
| |
| /* |
| * All indexed properties on the object are now dense, clear the indexed |
| * flag so that we will not start using sparse indexes again if we need |
| * to grow the object. |
| */ |
| if (!obj->clearFlag(cx, BaseShape::INDEXED)) |
| return ED_FAILED; |
| |
| return ED_OK; |
| } |
| |
| ObjectElements * |
| AllocateElements(ThreadSafeContext *tcx, JSObject *obj, uint32_t nelems) |
| { |
| #ifdef JSGC_GENERATIONAL |
| if (tcx->isJSContext()) { |
| JSContext *cx = tcx->asJSContext(); |
| return cx->runtime()->gcNursery.allocateElements(cx, obj, nelems); |
| } |
| #endif |
| |
| return static_cast<js::ObjectElements *>(tcx->malloc_(nelems * sizeof(HeapValue))); |
| } |
| |
| ObjectElements * |
| ReallocateElements(ThreadSafeContext *tcx, JSObject *obj, ObjectElements *oldHeader, |
| uint32_t oldCount, uint32_t newCount) |
| { |
| #ifdef JSGC_GENERATIONAL |
| if (tcx->isJSContext()) { |
| JSContext *cx = tcx->asJSContext(); |
| return cx->runtime()->gcNursery.reallocateElements(cx, obj, oldHeader, oldCount, newCount); |
| } |
| #endif |
| |
| return static_cast<js::ObjectElements *>(tcx->realloc_(oldHeader, oldCount * sizeof(HeapSlot), |
| newCount * sizeof(HeapSlot))); |
| } |
| |
| bool |
| JSObject::growElements(ThreadSafeContext *tcx, uint32_t newcap) |
| { |
| JS_ASSERT(isExtensible()); |
| JS_ASSERT_IF(isArray() && !arrayLengthIsWritable(), |
| newcap <= getArrayLength()); |
| |
| /* |
| * When an object with CAPACITY_DOUBLING_MAX or fewer elements needs to |
| * grow, double its capacity, to add N elements in amortized O(N) time. |
| * |
| * Above this limit, grow by 12.5% each time. Speed is still amortized |
| * O(N), with a higher constant factor, and we waste less space. |
| */ |
| static const size_t CAPACITY_DOUBLING_MAX = 1024 * 1024; |
| static const size_t CAPACITY_CHUNK = CAPACITY_DOUBLING_MAX / sizeof(Value); |
| |
| uint32_t oldcap = getDenseCapacity(); |
| JS_ASSERT(oldcap <= newcap); |
| |
| uint32_t nextsize = (oldcap <= CAPACITY_DOUBLING_MAX) |
| ? oldcap * 2 |
| : oldcap + (oldcap >> 3); |
| |
| uint32_t actualCapacity; |
| if (isArray() && !arrayLengthIsWritable()) { |
| // Preserve the |capacity <= length| invariant for arrays with |
| // non-writable length. See also js::ArraySetLength which initially |
| // enforces this requirement. |
| actualCapacity = newcap; |
| } else { |
| actualCapacity = Max(newcap, nextsize); |
| if (actualCapacity >= CAPACITY_CHUNK) |
| actualCapacity = JS_ROUNDUP(actualCapacity, CAPACITY_CHUNK); |
| else if (actualCapacity < SLOT_CAPACITY_MIN) |
| actualCapacity = SLOT_CAPACITY_MIN; |
| |
| /* Don't let nelements get close to wrapping around uint32_t. */ |
| if (actualCapacity >= NELEMENTS_LIMIT || actualCapacity < oldcap || actualCapacity < newcap) |
| return false; |
| } |
| |
| uint32_t initlen = getDenseInitializedLength(); |
|