Bug 430133 - Implement ES3.1's Object.defineProperty and Object.defineProperties. r=jorendorff

This commit is contained in:
Jeff Walden 2009-06-05 12:56:45 -07:00
parent d56b9f7fc4
commit 285792a4b4
17 changed files with 2041 additions and 76 deletions

View File

@ -317,3 +317,9 @@ MSG_DEF(JSMSG_DUPLICATE_PROPERTY, 234, 1, JSEXN_SYNTAXERR, "property name {0
MSG_DEF(JSMSG_DEPRECATED_DELETE_OPERAND, 235, 0, JSEXN_SYNTAXERR, "Applying the 'delete' operator to an unqualified name is deprecated")
MSG_DEF(JSMSG_DEPRECATED_ASSIGN, 236, 1, JSEXN_SYNTAXERR, "assignment to {0} is deprecated")
MSG_DEF(JSMSG_BAD_BINDING, 237, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated")
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 238, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 239, 0, JSEXN_TYPEERR, "object is not extensible")
MSG_DEF(JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, 240, 1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'")
MSG_DEF(JSMSG_CANT_APPEND_PROPERTIES_TO_UNWRITABLE_LENGTH_ARRAY, 241, 0, JSEXN_TYPEERR, "Can't add elements past the end of an array if its length property is unwritable")
MSG_DEF(JSMSG_DEFINE_ARRAY_LENGTH_UNSUPPORTED, 242, 0, JSEXN_INTERNALERR, "defining the length property on an array is not currently supported")
MSG_DEF(JSMSG_CANT_DEFINE_ARRAY_INDEX,243, 0, JSEXN_TYPEERR, "can't define array index property")

View File

@ -422,11 +422,11 @@ MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap)
if (OBJ_IS_NATIVE(obj2) &&
(attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
JSScopeProperty *sprop = (JSScopeProperty *) prop;
val = JSVAL_NULL;
val = JSVAL_VOID;
if (attrs & JSPROP_GETTER)
val = sprop->getterValue();
if (attrs & JSPROP_SETTER) {
if (val != JSVAL_NULL) {
if (val != JSVAL_VOID) {
/* Mark the getter, then set val to setter. */
ok = (MarkSharpObjects(cx, JSVAL_TO_OBJECT(val),
NULL)
@ -1693,45 +1693,45 @@ js_HasOwnPropertyHelper(JSContext *cx, JSLookupPropOp lookup, uintN argc,
if (!JS_ValueToId(cx, argc != 0 ? vp[2] : JSVAL_VOID, &id))
return JS_FALSE;
JSBool found;
JSObject *obj = JS_THIS_OBJECT(cx, vp);
if (!obj || !js_HasOwnProperty(cx, lookup, obj, id, &found))
JSObject *obj2;
JSProperty *prop;
if (!obj || !js_HasOwnProperty(cx, lookup, obj, id, &obj2, &prop))
return JS_FALSE;
*vp = BOOLEAN_TO_JSVAL(found);
if (prop) {
*vp = JSVAL_TRUE;
obj2->dropProperty(cx, prop);
} else {
*vp = JSVAL_FALSE;
}
return JS_TRUE;
}
JSBool
js_HasOwnProperty(JSContext *cx, JSLookupPropOp lookup, JSObject *obj, jsid id,
JSBool *foundp)
JSObject **objp, JSProperty **propp)
{
JSObject *obj2;
JSProperty *prop;
JSScopeProperty *sprop;
if (!lookup(cx, obj, id, &obj2, &prop))
if (!lookup(cx, obj, id, objp, propp))
return JS_FALSE;
if (!prop) {
*foundp = JS_FALSE;
} else if (obj2 == obj) {
*foundp = JS_TRUE;
} else {
JSClass *clasp;
JSExtendedClass *xclasp;
JSObject *outer;
if (!*propp)
return JS_TRUE;
clasp = OBJ_GET_CLASS(cx, obj2);
if (!(clasp->flags & JSCLASS_IS_EXTENDED) ||
!(xclasp = (JSExtendedClass *) clasp)->outerObject) {
outer = NULL;
} else {
outer = xclasp->outerObject(cx, obj2);
if (!outer)
return JS_FALSE;
}
if (outer == obj) {
*foundp = JS_TRUE;
} else if (OBJ_IS_NATIVE(obj2) && OBJ_GET_CLASS(cx, obj) == clasp) {
if (*objp == obj)
return JS_TRUE;
JSExtendedClass *xclasp;
JSObject *outer;
JSClass *clasp = (*objp)->getClass();
if (!(clasp->flags & JSCLASS_IS_EXTENDED) ||
!(xclasp = (JSExtendedClass *) clasp)->outerObject) {
outer = NULL;
} else {
outer = xclasp->outerObject(cx, *objp);
if (!outer)
return JS_FALSE;
}
if (outer != *objp) {
if (OBJ_IS_NATIVE(*objp) && obj->getClass() == clasp) {
/*
* The combination of JSPROP_SHARED and JSPROP_PERMANENT in a
* delegated property makes that property appear to be direct in
@ -1747,14 +1747,15 @@ js_HasOwnProperty(JSContext *cx, JSLookupPropOp lookup, JSObject *obj, jsid id,
* the property, there is no way to tell whether it is directly
* owned, or indirectly delegated.
*/
sprop = (JSScopeProperty *)prop;
*foundp = SPROP_IS_SHARED_PERMANENT(sprop);
if (!SPROP_IS_SHARED_PERMANENT((JSScopeProperty *) *propp)) {
(*objp)->dropProperty(cx, *propp);
*propp = NULL;
}
} else {
*foundp = JS_FALSE;
(*objp)->dropProperty(cx, *propp);
*propp = NULL;
}
}
if (prop)
obj2->dropProperty(cx, prop);
return JS_TRUE;
}
@ -1763,15 +1764,18 @@ static JSBool FASTCALL
Object_p_hasOwnProperty(JSContext* cx, JSObject* obj, JSString *str)
{
jsid id;
JSBool found;
JSObject *pobj;
JSProperty *prop;
if (!js_ValueToStringId(cx, STRING_TO_JSVAL(str), &id) ||
!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &found)) {
!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &pobj, &prop)) {
js_SetBuiltinError(cx);
return JSVAL_TO_BOOLEAN(JSVAL_VOID);
}
return found;
if (prop)
pobj->dropProperty(cx, prop);
return !!prop;
}
#endif
@ -2020,20 +2024,15 @@ obj_getOwnPropertyDescriptor(JSContext *cx, uintN argc, jsval *vp)
if (!JS_ValueToId(cx, argc >= 2 ? vp[3] : JSVAL_VOID, nameidr.addr()))
return JS_FALSE;
JSBool found;
if (!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, nameidr.id(), &found))
JSObject *pobj;
JSProperty *prop;
if (!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, nameidr.id(), &pobj, &prop))
return JS_FALSE;
if (!found) {
if (!prop) {
*vp = JSVAL_VOID;
return JS_TRUE;
}
JSObject *pobj;
JSProperty *prop;
if (!obj->lookupProperty(cx, nameidr.id(), &pobj, &prop))
return JS_FALSE;
JS_ASSERT(prop);
uintN attrs;
if (!pobj->getAttributes(cx, nameidr.id(), prop, &attrs)) {
pobj->dropProperty(cx, prop);
@ -2046,9 +2045,9 @@ obj_getOwnPropertyDescriptor(JSContext *cx, uintN argc, jsval *vp)
if (OBJ_IS_NATIVE(obj)) {
JSScopeProperty *sprop = reinterpret_cast<JSScopeProperty *>(prop);
if (attrs & JSPROP_GETTER)
roots[0] = js_CastAsObjectJSVal(sprop->getter);
roots[0] = sprop->getterValue();
if (attrs & JSPROP_SETTER)
roots[1] = js_CastAsObjectJSVal(sprop->setter);
roots[1] = sprop->setterValue();
}
pobj->dropProperty(cx, prop);
@ -2143,6 +2142,618 @@ obj_keys(JSContext *cx, uintN argc, jsval *vp)
return JS_TRUE;
}
static JSBool
HasProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp, JSBool* answerp)
{
if (!JS_HasPropertyById(cx, obj, id, answerp))
return JS_FALSE;
if (!*answerp) {
*vp = JSVAL_VOID;
return JS_TRUE;
}
return JS_GetPropertyById(cx, obj, id, vp);
}
PropertyDescriptor::PropertyDescriptor()
: id(INT_JSVAL_TO_JSID(JSVAL_ZERO)),
value(JSVAL_VOID),
get(JSVAL_VOID),
set(JSVAL_VOID),
attrs(0),
hasGet(false),
hasSet(false),
hasValue(false),
hasWritable(false),
hasEnumerable(false),
hasConfigurable(false)
{
}
bool
PropertyDescriptor::initialize(JSContext* cx, jsid id, jsval v)
{
this->id = id;
/* 8.10.5 step 1 */
if (JSVAL_IS_PRIMITIVE(v)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_NOT_NONNULL_OBJECT,
js_getter_str);
return false;
}
JSObject* desc = JSVAL_TO_OBJECT(v);
/* Start with the proper defaults. */
attrs = JSPROP_PERMANENT | JSPROP_READONLY;
JSBool hasProperty;
/* 8.10.5 step 3 */
if (!HasProperty(cx, desc, ATOM_TO_JSID(cx->runtime->atomState.enumerableAtom), &v,
&hasProperty)) {
return false;
}
if (hasProperty) {
hasEnumerable = JS_TRUE;
if (js_ValueToBoolean(v))
attrs |= JSPROP_ENUMERATE;
}
/* 8.10.5 step 4 */
if (!HasProperty(cx, desc, ATOM_TO_JSID(cx->runtime->atomState.configurableAtom), &v,
&hasProperty)) {
return false;
}
if (hasProperty) {
hasConfigurable = JS_TRUE;
if (js_ValueToBoolean(v))
attrs &= ~JSPROP_PERMANENT;
}
/* 8.10.5 step 5 */
if (!HasProperty(cx, desc, ATOM_TO_JSID(cx->runtime->atomState.valueAtom), &v, &hasProperty))
return false;
if (hasProperty) {
hasValue = true;
value = v;
}
/* 8.10.6 step 6 */
if (!HasProperty(cx, desc, ATOM_TO_JSID(cx->runtime->atomState.writableAtom), &v, &hasProperty))
return false;
if (hasProperty) {
hasWritable = JS_TRUE;
if (js_ValueToBoolean(v))
attrs &= ~JSPROP_READONLY;
}
/* 8.10.7 step 7 */
if (!HasProperty(cx, desc, ATOM_TO_JSID(cx->runtime->atomState.getAtom), &v, &hasProperty))
return false;
if (hasProperty) {
if ((JSVAL_IS_PRIMITIVE(v) || !js_IsCallable(JSVAL_TO_OBJECT(v), cx)) &&
v != JSVAL_VOID) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_GETTER_OR_SETTER,
js_getter_str);
return false;
}
hasGet = true;
get = v;
attrs |= JSPROP_GETTER | JSPROP_SHARED;
}
/* 8.10.7 step 8 */
if (!HasProperty(cx, desc, ATOM_TO_JSID(cx->runtime->atomState.setAtom), &v, &hasProperty))
return false;
if (hasProperty) {
if ((JSVAL_IS_PRIMITIVE(v) || !js_IsCallable(JSVAL_TO_OBJECT(v), cx)) &&
v != JSVAL_VOID) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_GETTER_OR_SETTER,
js_setter_str);
return false;
}
hasSet = true;
set = v;
attrs |= JSPROP_SETTER | JSPROP_SHARED;
}
/* 8.10.7 step 9 */
if ((hasGet || hasSet) && (hasValue || hasWritable)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INVALID_DESCRIPTOR);
return false;
}
return true;
}
typedef js::Vector<PropertyDescriptor, 1> PropertyDescriptorArray;
class AutoDescriptorArray : private JSTempValueRooter
{
private:
JSContext *cx;
PropertyDescriptorArray descriptors;
public:
AutoDescriptorArray(JSContext *cx)
: cx(cx), descriptors(cx)
{
JS_PUSH_TEMP_ROOT_TRACE(cx, trace, this);
}
~AutoDescriptorArray() {
JS_POP_TEMP_ROOT(cx, this);
}
bool append(PropertyDescriptor &desc) {
return descriptors.append(desc);
}
PropertyDescriptor& operator[](size_t i) {
JS_ASSERT(i < descriptors.length());
return descriptors[i];
}
private:
static void trace(JSTracer *trc, JSTempValueRooter *tvr) {
PropertyDescriptorArray &descs =
static_cast<AutoDescriptorArray *>(tvr)->descriptors;
for (size_t i = 0, len = descs.length(); i < len; i++) {
PropertyDescriptor &desc = descs[i];
JS_CALL_VALUE_TRACER(trc, desc.value, "PropertyDescriptor::value");
JS_CALL_VALUE_TRACER(trc, desc.get, "PropertyDescriptor::get");
JS_CALL_VALUE_TRACER(trc, desc.set, "PropertyDescriptor::set");
js_TraceId(trc, desc.id);
}
}
};
static JSBool
Reject(JSContext *cx, uintN errorNumber, bool throwError, jsid id, bool *rval)
{
if (throwError) {
jsid idstr;
if (!js_ValueToStringId(cx, ID_TO_VALUE(id), &idstr))
return JS_FALSE;
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber,
JS_GetStringBytes(JSVAL_TO_STRING(ID_TO_VALUE(idstr))));
return JS_FALSE;
}
*rval = false;
return JS_TRUE;
}
static JSBool
Reject(JSContext *cx, uintN errorNumber, bool throwError, bool *rval)
{
if (throwError) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber);
return JS_FALSE;
}
*rval = false;
return JS_TRUE;
}
static JSBool
Reject(JSContext *cx, JSObject *obj, JSProperty *prop, uintN errorNumber, bool throwError,
jsid id, bool *rval)
{
obj->dropProperty(cx, prop);
return Reject(cx, errorNumber, throwError, id, rval);
}
static JSBool
DefinePropertyObject(JSContext *cx, JSObject *obj, const PropertyDescriptor &desc,
bool throwError, bool *rval)
{
/* 8.12.9 step 1. */
JSProperty *current;
JSObject *obj2;
JS_ASSERT(obj->map->ops->lookupProperty == js_LookupProperty);
if (!js_HasOwnProperty(cx, js_LookupProperty, obj, desc.id, &obj2, &current))
return JS_FALSE;
JS_ASSERT(obj->map->ops->defineProperty == js_DefineProperty);
/* 8.12.9 steps 2-4. */
JSScope *scope = OBJ_SCOPE(obj);
if (!current) {
if (scope->sealed())
return Reject(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval);
*rval = true;
if (desc.isGenericDescriptor() || desc.isDataDescriptor()) {
JS_ASSERT(obj->map->ops->defineProperty == js_DefineProperty);
return js_DefineProperty(cx, obj, desc.id, desc.value,
JS_PropertyStub, JS_PropertyStub, desc.attrs);
}
JS_ASSERT(desc.isAccessorDescriptor());
/*
* Getters and setters are just like watchpoints from an access
* control point of view.
*/
jsval dummy;
uintN dummyAttrs;
JS_ASSERT(obj->map->ops->checkAccess == js_CheckAccess);
if (!js_CheckAccess(cx, obj, desc.id, JSACC_WATCH, &dummy, &dummyAttrs))
return JS_FALSE;
return js_DefineProperty(cx, obj, desc.id, JSVAL_VOID,
desc.getterObject() ? desc.getter() : JS_PropertyStub,
desc.setterObject() ? desc.setter() : JS_PropertyStub,
desc.attrs);
}
/* 8.12.9 steps 5-6 (note 5 is merely a special case of 6). */
jsval v = JSVAL_VOID;
/*
* In the special case of shared permanent properties, the "own" property
* can be found on a different object. In that case the returned property
* might not be native, except: the shared permanent property optimization
* is not applied if the objects have different classes (bug 320854), as
* must be enforced by js_HasOwnProperty for the JSScopeProperty cast below
* to be safe.
*/
JS_ASSERT(obj->getClass() == obj2->getClass());
JSScopeProperty *sprop = reinterpret_cast<JSScopeProperty *>(current);
do {
if (desc.isAccessorDescriptor()) {
if (!sprop->isAccessorDescriptor())
break;
if (desc.hasGet &&
!js_SameValue(desc.getterValue(),
(sprop->attrs & JSPROP_GETTER)
? sprop->getterValue()
: JSVAL_VOID, cx)) {
break;
}
if (desc.hasSet &&
!js_SameValue(desc.setterValue(),
(sprop->attrs & JSPROP_SETTER)
? sprop->setterValue()
: JSVAL_VOID, cx)) {
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 (sprop->isDataDescriptor()) {
/*
* Non-standard: if the property is non-configurable and is
* represented by a native getter or setter, don't permit
* redefinition. We expose properties with native getter/setter
* as though they were data properties, for the most part, but
* in this particular case we must worry about integrity
* concerns for JSAPI users who expected that
* permanent+getter/setter means precisely controlled behavior.
* If we permitted such redefinitions, such a property could be
* "fixed" to some specific previous value, no longer varying
* according to the intent of the native getter/setter for the
* property.
*
* Other engines expose properties of this nature using ECMA
* getter/setter pairs, but we can't because we use them even
* for properties which ECMA specifies as being true data
* descriptors ([].length, Function.length, /regex/.lastIndex,
* &c.). Longer-term perhaps we should convert such properties
* to use data descriptors (at which point representing a
* descriptor with native getter/setter as an accessor
* descriptor would be fine) and take a small memory hit, but
* for now we'll simply forbid their redefinition.
*/
if (!sprop->configurable() &&
(!SPROP_HAS_STUB_GETTER(sprop) || !SPROP_HAS_STUB_SETTER(sprop))) {
return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP,
throwError, desc.id, rval);
}
if (!js_NativeGet(cx, obj, obj2, sprop, JSGET_NO_METHOD_BARRIER, &v)) {
/* current was dropped when the failure occurred. */
return JS_FALSE;
}
}
if (desc.isDataDescriptor()) {
if (!sprop->isDataDescriptor())
break;
if (desc.hasValue && !js_SameValue(desc.value, v, cx))
break;
if (desc.hasWritable && desc.writable() != sprop->writable())
break;
} else {
/* The only fields in desc will be handled below. */
JS_ASSERT(desc.isGenericDescriptor());
}
}
if (desc.hasConfigurable && desc.configurable() != sprop->configurable())
break;
if (desc.hasEnumerable && desc.enumerable() != sprop->enumerable())
break;
/* The conditions imposed by step 5 or step 6 apply. */
obj2->dropProperty(cx, current);
*rval = true;
return JS_TRUE;
} while (0);
/* 8.12.9 step 7. */
if (!sprop->configurable()) {
/*
* Since [[Configurable]] defaults to false, we don't need to check
* whether it was specified. We can't do likewise for [[Enumerable]]
* because its putative value is used in a comparison -- a comparison
* whose result must always be false per spec if the [[Enumerable]]
* field is not present. Perfectly pellucid logic, eh?
*/
JS_ASSERT_IF(!desc.hasConfigurable, !desc.configurable());
if (desc.configurable() ||
(desc.hasEnumerable && desc.enumerable() != sprop->enumerable())) {
return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, throwError,
desc.id, rval);
}
}
if (desc.isGenericDescriptor()) {
/* 8.12.9 step 8, no validation required */
} else if (desc.isDataDescriptor() != sprop->isDataDescriptor()) {
/* 8.12.9 step 9. */
if (!sprop->configurable()) {
return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP,
throwError, desc.id, rval);
}
} else if (desc.isDataDescriptor() && sprop->isDataDescriptor()) {
/* 8.12.9 step 10. */
if (!sprop->configurable() && !sprop->writable()) {
if ((desc.hasWritable && desc.writable()) ||
(desc.hasValue && !js_SameValue(desc.value, v, cx))) {
return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP,
throwError, desc.id, rval);
}
}
} else {
/* 8.12.9 step 11. */
JS_ASSERT(desc.isAccessorDescriptor() && sprop->isAccessorDescriptor());
if (!sprop->configurable()) {
if ((desc.hasSet &&
!js_SameValue(desc.setterValue(),
(sprop->attrs & JSPROP_SETTER) ? sprop->setterValue() : JSVAL_VOID,
cx)) ||
(desc.hasGet &&
!js_SameValue(desc.getterValue(),
(sprop->attrs & JSPROP_GETTER) ? sprop->getterValue() : JSVAL_VOID,
cx)))
{
return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP,
throwError, desc.id, rval);
}
}
}
/* 8.12.9 step 12. */
uintN attrs;
JSPropertyOp getter, setter;
if (desc.isGenericDescriptor()) {
uintN changed = 0;
if (desc.hasConfigurable)
changed |= JSPROP_PERMANENT;
if (desc.hasEnumerable)
changed |= JSPROP_ENUMERATE;
attrs = (sprop->attrs & ~changed) | (desc.attrs & changed);
getter = sprop->getter;
setter = sprop->setter;
} else if (desc.isDataDescriptor()) {
uintN unchanged = 0;
if (!desc.hasConfigurable)
unchanged |= JSPROP_PERMANENT;
if (!desc.hasEnumerable)
unchanged |= JSPROP_ENUMERATE;
if (!desc.hasWritable)
unchanged |= JSPROP_READONLY;
if (desc.hasValue)
v = desc.value;
attrs = (desc.attrs & ~unchanged) | (sprop->attrs & unchanged);
getter = setter = JS_PropertyStub;
} else {
JS_ASSERT(desc.isAccessorDescriptor());
/*
* Getters and setters are just like watchpoints from an access
* control point of view.
*/
jsval dummy;
JS_ASSERT(obj2->map->ops->checkAccess == js_CheckAccess);
if (!js_CheckAccess(cx, obj2, desc.id, JSACC_WATCH, &dummy, &attrs)) {
obj2->dropProperty(cx, current);
return JS_FALSE;
}
/* 8.12.9 step 12. */
uintN changed = 0;
if (desc.hasConfigurable)
changed |= JSPROP_PERMANENT;
if (desc.hasEnumerable)
changed |= JSPROP_ENUMERATE;
if (desc.hasGet)
changed |= JSPROP_GETTER | JSPROP_SHARED;
if (desc.hasSet)
changed |= JSPROP_SETTER | JSPROP_SHARED;
attrs = (desc.attrs & changed) | (sprop->attrs & ~changed);
if (desc.hasGet)
getter = desc.getterObject() ? desc.getter() : JS_PropertyStub;
else
getter = sprop->getter;
if (desc.hasSet)
setter = desc.setterObject() ? desc.setter() : JS_PropertyStub;
else
setter = sprop->setter;
}
*rval = true;
obj2->dropProperty(cx, current);
return js_DefineProperty(cx, obj, desc.id, v, getter, setter, attrs);
}
static JSBool
DefinePropertyArray(JSContext *cx, JSObject *obj, const PropertyDescriptor &desc,
bool throwError, bool *rval)
{
/*
* We probably should optimize dense array property definitions where
* the descriptor describes a traditional array property (enumerable,
* configurable, writable, numeric index or length without altering its
* attributes). Such definitions are probably unlikely, so we don't bother
* for now.
*/
if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_MakeArraySlow(cx, obj))
return JS_FALSE;
jsuint oldLen = obj->fslots[JSSLOT_ARRAY_LENGTH];
if (desc.id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)) {
/*
* Our optimization of storage of the length property of arrays makes
* it very difficult to properly implement defining the property. For
* now simply throw an exception (NB: not merely Reject) on any attempt
* to define the "length" property, rather than attempting to implement
* some difficult-for-authors-to-grasp subset of that functionality.
*/
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEFINE_ARRAY_LENGTH_UNSUPPORTED);
return JS_FALSE;
}
uint32 index;
if (js_IdIsIndex(desc.id, &index)) {
/*
// Disabled until we support defining "length":
if (index >= oldLen && lengthPropertyNotWritable())
return ThrowTypeError(cx, JSMSG_CANT_APPEND_PROPERTIES_TO_UNWRITABLE_LENGTH_ARRAY);
*/
if (!DefinePropertyObject(cx, obj, desc, false, rval))
return JS_FALSE;
if (!*rval)
return Reject(cx, JSMSG_CANT_DEFINE_ARRAY_INDEX, throwError, rval);
if (index >= oldLen) {
JS_ASSERT(index != UINT32_MAX);
obj->fslots[JSSLOT_ARRAY_LENGTH] = index + 1;
}
*rval = true;
return JS_TRUE;
}
return DefinePropertyObject(cx, obj, desc, throwError, rval);
}
static JSBool
DefineProperty(JSContext *cx, JSObject *obj, const PropertyDescriptor &desc, bool throwError,
bool *rval)
{
if (OBJ_IS_ARRAY(cx, obj))
return DefinePropertyArray(cx, obj, desc, throwError, rval);
if (!OBJ_IS_NATIVE(obj))
return Reject(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval);
return DefinePropertyObject(cx, obj, desc, throwError, rval);
}
/* ES5 15.2.3.6: Object.defineProperty(O, P, Attributes) */
static JSBool
obj_defineProperty(JSContext* cx, uintN argc, jsval* vp)
{
/* 15.2.3.6 steps 1 and 5. */
jsval v = argc == 0 ? JSVAL_VOID : vp[2];
if (JSVAL_IS_PRIMITIVE(v)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_NOT_NONNULL_OBJECT,
js_getter_str);
return JS_FALSE;
}
*vp = vp[2];
JSObject* obj = JSVAL_TO_OBJECT(*vp);
/* 15.2.3.6 step 2. */
JSAutoTempIdRooter nameidr(cx);
if (!JS_ValueToId(cx, argc >= 2 ? vp[3] : JSVAL_VOID, nameidr.addr()))
return JS_FALSE;
/* 15.2.3.6 step 3. */
AutoDescriptorArray descs(cx);
PropertyDescriptor desc;
if (!desc.initialize(cx, nameidr.id(), argc >= 3 ? vp[4] : JSVAL_VOID) || !descs.append(desc))
return JS_FALSE;
/* 15.2.3.6 step 4 */
bool dummy;
return DefineProperty(cx, obj, desc, true, &dummy);
}
/* ES5 15.2.3.7: Object.defineProperties(O, Properties) */
static JSBool
obj_defineProperties(JSContext* cx, uintN argc, jsval* vp)
{
/* 15.2.3.6 steps 1 and 5. */
jsval v = argc > 0 ? vp[2] : JSVAL_VOID;
if (JSVAL_IS_PRIMITIVE(v)) {
js_ReportValueError(cx, JSMSG_NOT_NONNULL_OBJECT, -1, v, NULL);
return JS_FALSE;
}
*vp = vp[2];
v = argc > 1 ? vp[3] : JSVAL_VOID;
if (JSVAL_IS_PRIMITIVE(v)) {
js_ReportValueError(cx, JSMSG_NOT_NONNULL_OBJECT, -1, v, NULL);
return JS_FALSE;
}
JSObject* props = JSVAL_TO_OBJECT(v);
JSAutoIdArray ida(cx, JS_Enumerate(cx, props));
if (!ida)
return JS_FALSE;
AutoDescriptorArray descs(cx);
size_t len = ida.length();
for (size_t i = 0; i < len; i++) {
PropertyDescriptor desc;
jsid id = ida[i];
if (!JS_GetPropertyById(cx, props, id, &vp[1]) || !desc.initialize(cx, id, vp[1]) ||
!descs.append(desc)) {
return JS_FALSE;
}
}
JSObject *obj = JSVAL_TO_OBJECT(*vp);
bool dummy;
for (size_t i = 0; i < len; i++) {
if (!DefineProperty(cx, obj, descs[i], true, &dummy))
return JS_FALSE;
}
return JS_TRUE;
}
#if JS_HAS_OBJ_WATCHPOINT
const char js_watch_str[] = "watch";
@ -2192,6 +2803,8 @@ static JSFunctionSpec object_static_methods[] = {
JS_FN("getPrototypeOf", obj_getPrototypeOf, 1,0),
JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2,0),
JS_FN("keys", obj_keys, 1,0),
JS_FN("defineProperty", obj_defineProperty, 3,0),
JS_FN("defineProperties", obj_defineProperties, 2,0),
JS_FS_END
};

View File

@ -49,8 +49,85 @@
* is reference counted and the slot vector is malloc'ed.
*/
#include "jshash.h" /* Added by JSIFY */
#include "jsprvtd.h"
#include "jspubtd.h"
#include "jsprvtd.h"
#include "jsstdint.h"
/*
* A representation of ECMA-262 ed. 5's internal property descriptor data
* structure.
*/
struct PropertyDescriptor {
PropertyDescriptor();
/* 8.10.5 ToPropertyDescriptor(Obj) */
bool initialize(JSContext* cx, jsid id, jsval v);
/* 8.10.1 IsAccessorDescriptor(desc) */
bool isAccessorDescriptor() const {
return hasGet || hasSet;
}
/* 8.10.2 IsDataDescriptor(desc) */
bool isDataDescriptor() const {
return hasValue || hasWritable;
}
/* 8.10.3 IsGenericDescriptor(desc) */
bool isGenericDescriptor() const {
return !isAccessorDescriptor() && !isDataDescriptor();
}
bool configurable() const {
return (attrs & JSPROP_PERMANENT) == 0;
}
bool enumerable() const {
return (attrs & JSPROP_ENUMERATE) != 0;
}
bool writable() const {
return (attrs & JSPROP_READONLY) == 0;
}
JSObject* getterObject() const {
return get != JSVAL_VOID ? JSVAL_TO_OBJECT(get) : NULL;
}
JSObject* setterObject() const {
return set != JSVAL_VOID ? JSVAL_TO_OBJECT(set) : NULL;
}
jsval getterValue() const {
return get;
}
jsval setterValue() const {
return set;
}
JSPropertyOp getter() const {
return js_CastAsPropertyOp(getterObject());
}
JSPropertyOp setter() const {
return js_CastAsPropertyOp(setterObject());
}
static void traceDescriptorArray(JSTracer* trc, JSObject* obj);
static void finalizeDescriptorArray(JSContext* cx, JSObject* obj);
jsid id;
jsval value, get, set;
/* Property descriptor boolean fields. */
uint8_t attrs;
/* Bits indicating which values are set. */
bool hasGet : 1;
bool hasSet : 1;
bool hasValue : 1;
bool hasWritable : 1;
bool hasEnumerable : 1;
bool hasConfigurable : 1;
};
JS_BEGIN_EXTERN_C
@ -581,7 +658,7 @@ js_HasOwnPropertyHelper(JSContext *cx, JSLookupPropOp lookup, uintN argc,
extern JSBool
js_HasOwnProperty(JSContext *cx, JSLookupPropOp lookup, JSObject *obj, jsid id,
JSBool *foundp);
JSObject **objp, JSProperty **propp);
extern JSBool
js_PropertyIsEnumerable(JSContext *cx, JSObject *obj, jsid id, jsval *vp);

View File

@ -312,15 +312,18 @@ JO(JSContext *cx, jsval *vp, StringifyContext *scx)
// Don't include prototype properties, since this operation is
// supposed to be implemented as if by ES3.1 Object.keys()
jsid id;
JSBool found = JS_FALSE;
JSObject *obj2;
JSProperty *prop;
if (!js_ValueToStringId(cx, STRING_TO_JSVAL(ks), &id) ||
!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &found)) {
!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &obj2, &prop)) {
goto error_break;
}
if (!found)
if (!prop)
continue;
obj2->dropProperty(cx, prop);
if (!JS_GetPropertyById(cx, obj, id, &outputValue))
goto error_break;

View File

@ -159,6 +159,12 @@ class Vector;
/* Common instantiations. */
typedef js::Vector<jschar, 32> JSCharBuffer;
static inline JSPropertyOp
js_CastAsPropertyOp(JSObject *object)
{
return JS_DATA_TO_FUNC_PTR(JSPropertyOp, object);
}
} /* export "C++" */
#endif /* __cplusplus */

View File

@ -1228,7 +1228,6 @@ JSScope::addProperty(JSContext *cx, jsid id,
JS_ASSERT(!JSVAL_IS_NULL(id));
JS_ASSERT_IF(attrs & JSPROP_GETTER, getter);
JS_ASSERT_IF(attrs & JSPROP_SETTER, setter);
JS_ASSERT_IF(!cx->runtime->gcRegenShapes,
hasRegenFlag(cx->runtime->gcRegenShapesScopeFlag));
@ -1810,11 +1809,11 @@ JSScopeProperty::trace(JSTracer *trc)
#if JS_HAS_GETTER_SETTER
if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
if (attrs & JSPROP_GETTER) {
if ((attrs & JSPROP_GETTER) && getter) {
JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 0);
JS_CallTracer(trc, getterObject(), JSTRACE_OBJECT);
}
if (attrs & JSPROP_SETTER) {
if ((attrs & JSPROP_SETTER) && setter) {
JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 1);
JS_CallTracer(trc, setterObject(), JSTRACE_OBJECT);
}

View File

@ -507,12 +507,6 @@ js_CastAsObjectJSVal(JSPropertyOp op)
return OBJECT_TO_JSVAL(JS_FUNC_TO_DATA_PTR(JSObject *, op));
}
inline JSPropertyOp
js_CastAsPropertyOp(JSObject *object)
{
return JS_DATA_TO_FUNC_PTR(JSPropertyOp, object);
}
struct JSScopeProperty {
jsid id; /* int-tagged jsval/untagged JSAtom* */
JSPropertyOp getter; /* getter and setter hooks or objects */
@ -553,34 +547,43 @@ struct JSScopeProperty {
return js_CastAsObjectJSVal(getter);
}
bool hasGetterObject() const {
return attrs & JSPROP_GETTER;
}
JSObject *getterObject() const {
JS_ASSERT(hasGetterObject());
JS_ASSERT(attrs & JSPROP_GETTER);
return js_CastAsObject(getter);
}
jsval getterValue() const {
JS_ASSERT(hasGetterObject());
return js_CastAsObjectJSVal(getter);
JS_ASSERT(attrs & JSPROP_GETTER);
jsval getterVal = getter ? js_CastAsObjectJSVal(getter) : JSVAL_VOID;
JS_ASSERT_IF(getter, VALUE_IS_FUNCTION(BOGUS_CX, getterVal));
return getterVal;
}
bool hasSetterObject() const {
return attrs & JSPROP_SETTER;
}
JSObject *setterObject() const {
JS_ASSERT(hasSetterObject());
JS_ASSERT((attrs & JSPROP_SETTER) && setter);
return js_CastAsObject(setter);
}
jsval setterValue() const {
JS_ASSERT(hasSetterObject());
return js_CastAsObjectJSVal(setter);
JS_ASSERT(attrs & JSPROP_SETTER);
jsval setterVal = setter ? js_CastAsObjectJSVal(setter) : JSVAL_VOID;
JS_ASSERT_IF(setter, VALUE_IS_FUNCTION(BOGUS_CX, setterVal));
return setterVal;
}
bool get(JSContext* cx, JSObject* obj, JSObject *pobj, jsval* vp);
bool set(JSContext* cx, JSObject* obj, jsval* vp);
void trace(JSTracer *trc);
bool configurable() { return (attrs & JSPROP_PERMANENT) == 0; }
bool enumerable() { return (attrs & JSPROP_ENUMERATE) != 0; }
bool writable() { return (attrs & JSPROP_READONLY) == 0; }
bool isDataDescriptor() {
return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) == 0;
}
bool isAccessorDescriptor() {
return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) != 0;
}
};
/* JSScopeProperty pointer tag bit indicating a collision. */

View File

@ -0,0 +1,35 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var gTestfile = '15.2.3.6-function-length.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary = 'ES5 Object.defineProperty(O, P, Attributes): Function.length';
print(BUGNUMBER + ": " + summary);
load("ecma_5/Object/defineProperty-setup.js");
/**************
* BEGIN TEST *
**************/
try
{
new TestRunner().runFunctionLengthTests();
}
catch (e)
{
throw "Error thrown during testing: " + e +
" at line " + e.lineNumber + "\n" +
(e.stack
? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n"
: "");
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete!");

View File

@ -0,0 +1,65 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var gTestfile = '15.2.3.6-miscellaneous.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary = 'ES5 Object.defineProperty(O, P, Attributes)';
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
var o = [];
Object.defineProperty(o, 0, { value: 17 });
var desc = Object.getOwnPropertyDescriptor(o, 0);
assertEq(desc !== undefined, true);
assertEq(desc.value, 17);
assertEq(desc.enumerable, false);
assertEq(desc.configurable, false);
assertEq(desc.writable, false);
desc = Object.getOwnPropertyDescriptor(o, "length");
assertEq(desc !== undefined, true);
assertEq(desc.enumerable, false);
assertEq(desc.configurable, false);
assertEq(desc.writable, true);
assertEq(desc.value, 1);
assertEq(o.length, 1);
Object.defineProperty(o, "foobar",
{ value: 42, enumerable: false, configurable: true });
assertEq(o.foobar, 42);
desc = Object.getOwnPropertyDescriptor(o, "foobar");
assertEq(desc !== undefined, true);
assertEq(desc.value, 42);
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, false);
var called = false;
o = { set x(a) { called = true; } };
Object.defineProperty(o, "x", { get: function() { return "get"; } });
desc = Object.getOwnPropertyDescriptor(o, "x");
assertEq("set" in desc, true);
assertEq("get" in desc, true);
assertEq(called, false);
o.x = 13;
assertEq(called, true);
var toSource = Object.prototype.toSource || function() { };
toSource.call(o); // a test for this not crashing
/*
* XXX need tests for Object.defineProperty(array, "length", { ... }) when we
* support it!
*/
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("All tests passed!");

View File

@ -0,0 +1,35 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var gTestfile = '15.2.3.6-new-definition.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary = 'ES5 Object.defineProperty(O, P, Attributes): new definition';
print(BUGNUMBER + ": " + summary);
load("ecma_5/Object/defineProperty-setup.js");
/**************
* BEGIN TEST *
**************/
try
{
new TestRunner().runNotPresentTests();
}
catch (e)
{
throw "Error thrown during testing: " + e +
" at line " + e.lineNumber + "\n" +
(e.stack
? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n"
: "");
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete!");

View File

@ -0,0 +1,39 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var PART = 1, PARTS = 4;
var gTestfile = '15.2.3.6-redefinition-1-of-4.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary =
'ES5 Object.defineProperty(O, P, Attributes): redefinition ' +
PART + ' of ' + PARTS;
print(BUGNUMBER + ": " + summary);
load("ecma_5/Object/defineProperty-setup.js");
/**************
* BEGIN TEST *
**************/
try
{
new TestRunner().runPropertyPresentTestsFraction(PART, PARTS);
}
catch (e)
{
throw "Error thrown during testing: " + e +
" at line " + e.lineNumber + "\n" +
(e.stack
? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n"
: "");
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete!");

View File

@ -0,0 +1,39 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var PART = 2, PARTS = 4;
var gTestfile = '15.2.3.6-redefinition-2-of-4.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary =
'ES5 Object.defineProperty(O, P, Attributes): redefinition ' +
PART + ' of ' + PARTS;
print(BUGNUMBER + ": " + summary);
load("ecma_5/Object/defineProperty-setup.js");
/**************
* BEGIN TEST *
**************/
try
{
new TestRunner().runPropertyPresentTestsFraction(PART, PARTS);
}
catch (e)
{
throw "Error thrown during testing: " + e +
" at line " + e.lineNumber + "\n" +
(e.stack
? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n"
: "");
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete!");

View File

@ -0,0 +1,39 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var PART = 3, PARTS = 4;
var gTestfile = '15.2.3.6-redefinition-3-of-4.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary =
'ES5 Object.defineProperty(O, P, Attributes): redefinition ' +
PART + ' of ' + PARTS;
print(BUGNUMBER + ": " + summary);
load("ecma_5/Object/defineProperty-setup.js");
/**************
* BEGIN TEST *
**************/
try
{
new TestRunner().runPropertyPresentTestsFraction(PART, PARTS);
}
catch (e)
{
throw "Error thrown during testing: " + e +
" at line " + e.lineNumber + "\n" +
(e.stack
? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n"
: "");
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete!");

View File

@ -0,0 +1,39 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
var PART = 4, PARTS = 4;
var gTestfile = '15.2.3.6-redefinition-4-of-4.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary =
'ES5 Object.defineProperty(O, P, Attributes): redefinition ' +
PART + ' of ' + PARTS;
print(BUGNUMBER + ": " + summary);
load("ecma_5/Object/defineProperty-setup.js");
/**************
* BEGIN TEST *
**************/
try
{
new TestRunner().runPropertyPresentTestsFraction(PART, PARTS);
}
catch (e)
{
throw "Error thrown during testing: " + e +
" at line " + e.lineNumber + "\n" +
(e.stack
? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n"
: "");
}
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete!");

View File

@ -0,0 +1,83 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var gTestfile = '15.2.3.7-01.js';
//-----------------------------------------------------------------------------
var BUGNUMBER = 430133;
var summary = 'ES5 Object.defineProperties(O, Properties)';
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
assertEq("defineProperties" in Object, true);
assertEq(Object.defineProperties.length, 2);
var o, props, desc, passed;
o = {};
props =
{
a: { value: 17, enumerable: true, configurable: true, writable: true },
b: { value: 42, enumerable: false, configurable: false, writable: false }
};
Object.defineProperties(o, props);
assertEq("a" in o, true);
assertEq("b" in o, true);
desc = Object.getOwnPropertyDescriptor(o, "a");
assertEq(desc.value, 17);
assertEq(desc.enumerable, true);
assertEq(desc.configurable, true);
assertEq(desc.writable, true);
desc = Object.getOwnPropertyDescriptor(o, "b");
assertEq(desc.value, 42);
assertEq(desc.enumerable, false);
assertEq(desc.configurable, false);
assertEq(desc.writable, false);
props =
{
c: { value: NaN, enumerable: false, configurable: true, writable: true },
b: { value: 44 }
};
var error = "before";
try
{
Object.defineProperties(o, props);
error = "no exception thrown";
}
catch (e)
{
if (e instanceof TypeError)
error = "typeerror";
else
error = "bad exception: " + e;
}
assertEq(error, "typeerror", "didn't throw or threw wrongly");
assertEq("c" in o, true, "new property added");
assertEq(o.b, 42, "old property value preserved");
function Properties() { }
Properties.prototype = { b: { value: 42, enumerable: true } };
props = new Properties();
Object.defineProperty(props, "a", { enumerable: false });
o = {};
Object.defineProperties(o, props);
assertEq("a" in o, false);
assertEq(Object.getOwnPropertyDescriptor(o, "a"), undefined,
"Object.defineProperties(O, Properties) should only use enumerable " +
"properties on Properties");
assertEq("b" in o, false);
assertEq(Object.getOwnPropertyDescriptor(o, "b"), undefined,
"Object.defineProperties(O, Properties) should only use enumerable " +
"properties directly on Properties");
/******************************************************************************/
reportCompare(true, true);
print("All tests passed!");

View File

@ -0,0 +1,876 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
assertEq("defineProperty" in Object, true);
assertEq(Object.defineProperty.length, 3);
if (!Object.prototype.toSource)
{
Object.defineProperty(Object.prototype, "toSource",
{
value: function toSource()
{
if (this instanceof RegExp)
{
var v = "new RegExp(" + uneval(this.source);
var f = (this.multiline ? "m" : "") +
(this.global ? "g" : "") +
(this.ignoreCase ? "i" : "");
return v + (f ? ", '" + f + "'" : "") + ")";
}
return JSON.stringify(this);
},
enumerable: false,
configurable: true,
writable: true
});
}
if (!("uneval" in this))
{
Object.defineProperty(this, "uneval",
{
value: function uneval(v)
{
if (v === null)
return "null";
if (typeof v === "object")
return v.toSource();
if (typeof v === "string")
{
v = JSON.stringify({v:v});
return v.substring(5, v.length - 1);
}
return "" + v;
},
enumerable: false,
configurable: true,
writable: true
});
}
// reimplemented for the benefit of engines which don't have this helper
function assertEq(v1, v2, m)
{
if (!SameValue(v1, v2))
{
throw "assertion failed: " +
"got " + uneval(v1) + ", expected " + uneval(v2) +
(m ? ": " + m : "");
}
}
function SameValue(v1, v2)
{
if (v1 === 0 && v2 === 0)
return 1 / v1 === 1 / v2;
if (v1 !== v1 && v2 !== v2)
return true;
return v1 === v2;
}
function PropertyDescriptor(pd)
{
if (pd)
this.update(pd);
}
PropertyDescriptor.prototype.update = function update(pd)
{
if ("get" in pd)
this.get = pd.get;
if ("set" in pd)
this.set = pd.set;
if ("configurable" in pd)
this.configurable = pd.configurable;
if ("writable" in pd)
this.writable = pd.writable;
if ("enumerable" in pd)
this.enumerable = pd.enumerable;
if ("value" in pd)
this.value = pd.value;
};
PropertyDescriptor.prototype.convertToDataDescriptor = function convertToDataDescriptor()
{
delete this.get;
delete this.set;
this.writable = false;
this.value = undefined;
};
PropertyDescriptor.prototype.convertToAccessorDescriptor = function convertToAccessorDescriptor()
{
delete this.writable;
delete this.value;
this.get = undefined;
this.set = undefined;
};
function compareDescriptors(d1, d2)
{
if (d1 === undefined)
{
assertEq(d2, undefined, "non-descriptors");
return;
}
if (d2 === undefined)
{
assertEq(true, false, "descriptor-equality mismatch: " + uneval(d1) + ", " + uneval(d2));
return;
}
var props = ["value", "get", "set", "enumerable", "configurable", "writable"];
for (var i = 0, sz = props.length; i < sz; i++)
{
var p = props[i];
assertEq(p in d1, p in d2, p + " different in d1/d2");
if (p in d1)
assertEq(d1[p], d2[p], p);
}
}
function examine(desc, field, allowDefault)
{
if (field in desc)
return desc[field];
assertEq(allowDefault, true, "reimplementation error");
switch (field)
{
case "value":
case "get":
case "set":
return undefined;
case "writable":
case "enumerable":
case "configurable":
return false;
default:
assertEq(true, false, "bad field name: " + field);
}
}
function IsAccessorDescriptor(desc)
{
if (!desc)
return false;
if (!("get" in desc) && !("set" in desc))
return false;
return true;
}
function IsDataDescriptor(desc)
{
if (!desc)
return false;
if (!("value" in desc) && !("writable" in desc))
return false;
return true;
}
function IsGenericDescriptor(desc)
{
if (!desc)
return false;
if (!IsAccessorDescriptor(desc) && !IsDataDescriptor(desc))
return true;
return false;
}
function CustomObject()
{
this.properties = {};
this.extensible = true;
}
CustomObject.prototype =
{
_reject: function _reject(throwing, msg)
{
if (throwing)
throw new TypeError(msg + "; rejected!");
return false;
},
defineOwnProperty: function defineOwnProperty(propname, desc, throwing)
{
assertEq(typeof propname, "string", "non-string propname");
// Step 1.
var current = this.properties[propname];
// Step 2.
var extensible = this.extensible;
// Step 3.
if (current === undefined && !extensible)
return this._reject(throwing, "object not extensible");
// Step 4.
if (current === undefined && extensible)
{
var p;
// Step 4(a).
if (IsGenericDescriptor(desc) || IsDataDescriptor(desc))
{
p = new PropertyDescriptor();
p.value = examine(desc, "value", true);
p.writable = examine(desc, "writable", true);
p.enumerable = examine(desc, "enumerable", true);
p.configurable = examine(desc, "configurable", true);
}
// Step 4(b).
else
{
p = new PropertyDescriptor();
p.get = examine(desc, "get", true);
p.set = examine(desc, "set", true);
p.enumerable = examine(desc, "enumerable", true);
p.configurable = examine(desc, "configurable", true);
}
this.properties[propname] = p;
// Step 4(c).
return true;
}
// Step 5.
if (!("value" in desc) && !("get" in desc) && !("set" in desc) &&
!("writable" in desc) && !("enumerable" in desc) &&
!("configurable" in desc))
{
return;
}
// Step 6.
do
{
if ("value" in desc)
{
if (!("value" in current) || !SameValue(desc.value, current.value))
break;
}
if ("get" in desc)
{
if (!("get" in current) || !SameValue(desc.get, current.get))
break;
}
if ("set" in desc)
{
if (!("set" in current) || !SameValue(desc.set, current.set))
break;
}
if ("writable" in desc)
{
if (!("writable" in current) ||
!SameValue(desc.writable, current.writable))
{
break;
}
}
if ("enumerable" in desc)
{
if (!("enumerable" in current) ||
!SameValue(desc.enumerable, current.enumerable))
{
break;
}
}
if ("configurable" in desc)
{
if (!("configurable" in current) ||
!SameValue(desc.configurable, current.configurable))
{
break;
}
}
// all fields in desc also in current, with the same values
return true;
}
while (false);
// Step 7.
if (!examine(current, "configurable"))
{
if ("configurable" in desc && examine(desc, "configurable"))
return this._reject(throwing, "can't make configurable again");
if ("enumerable" in desc &&
examine(current, "enumerable") !== examine(desc, "enumerable"))
{
return this._reject(throwing, "can't change enumerability");
}
}
// Step 8.
if (IsGenericDescriptor(desc))
{
// do nothing
}
// Step 9.
else if (IsDataDescriptor(current) !== IsDataDescriptor(desc))
{
// Step 9(a).
if (!examine(current, "configurable"))
return this._reject(throwing, "can't change unconfigurable descriptor's type");
// Step 9(b).
if (IsDataDescriptor(current))
current.convertToAccessorDescriptor();
// Step 9(c).
else
current.convertToDataDescriptor();
}
// Step 10.
else if (IsDataDescriptor(current) && IsDataDescriptor(desc))
{
// Step 10(a)
if (!examine(current, "configurable"))
{
// Step 10(a).i.
if (!examine(current, "writable") &&
"writable" in desc && examine(desc, "writable"))
{
return this._reject(throwing, "can't make data property writable again");
}
// Step 10(a).ii.
if (!examine(current, "writable"))
{
if ("value" in desc &&
!SameValue(examine(desc, "value"), examine(current, "value")))
{
return this._reject(throwing, "can't change value if not writable");
}
}
}
// Step 10(b).
else
{
assertEq(examine(current, "configurable"), true,
"spec bug step 10(b)");
}
}
// Step 11.
else
{
assertEq(IsAccessorDescriptor(current) && IsAccessorDescriptor(desc),
true,
"spec bug");
// Step 11(a).
if (!examine(current, "configurable"))
{
// Step 11(a).i.
if ("set" in desc &&
!SameValue(examine(desc, "set"), examine(current, "set")))
{
return this._reject(throwing, "can't change setter if not configurable");
}
// Step 11(a).ii.
if ("get" in desc &&
!SameValue(examine(desc, "get"), examine(current, "get")))
{
return this._reject(throwing, "can't change getter if not configurable");
}
}
}
// Step 12.
current.update(desc);
// Step 13.
return true;
}
};
function IsCallable(v)
{
return typeof v === "undefined" || typeof v === "function";
}
var NativeTest =
{
newObject: function newObject()
{
return {};
},
defineProperty: function defineProperty(obj, propname, propdesc)
{
Object.defineProperty(obj, propname, propdesc);
},
getDescriptor: function getDescriptor(obj, propname)
{
return Object.getOwnPropertyDescriptor(obj, propname);
}
};
var ReimplTest =
{
newObject: function newObject()
{
return new CustomObject();
},
defineProperty: function defineProperty(obj, propname, propdesc)
{
assertEq(obj instanceof CustomObject, true, "obj not instanceof CustomObject");
if ("get" in propdesc || "set" in propdesc)
{
if ("value" in propdesc || "writable" in propdesc)
throw new TypeError("get/set and value/writable");
if (!IsCallable(propdesc.get))
throw new TypeError("get defined, uncallable");
if (!IsCallable(propdesc.set))
throw new TypeError("set defined, uncallable");
}
return obj.defineOwnProperty(propname, propdesc, true);
},
getDescriptor: function getDescriptor(obj, propname)
{
if (!(propname in obj.properties))
return undefined;
return new PropertyDescriptor(obj.properties[propname]);
}
};
var JSVAL_INT_MAX = Math.pow(2, 30) - 1;
var JSVAL_INT_MIN = -Math.pow(2, 30);
function isValidDescriptor(propdesc)
{
if ("get" in propdesc || "set" in propdesc)
{
if ("value" in propdesc || "writable" in propdesc)
return false;
// We permit null here simply because this test's author believes the
// implementation may sometime be susceptible to making mistakes in this
// regard and would prefer to be cautious.
if (propdesc.get !== null && propdesc.get !== undefined && !IsCallable(propdesc.get))
return false;
if (propdesc.set !== null && propdesc.set !== undefined && !IsCallable(propdesc.set))
return false;
}
return true;
}
var OMIT = {};
var VALUES =
[-Infinity, JSVAL_INT_MIN, -0, +0, 1.5, JSVAL_INT_MAX, Infinity,
NaN, "foo", "bar", null, undefined, true, false, {}, /a/, OMIT];
var GETS =
[undefined, function get1() { return 1; }, function get2() { return 2; },
null, 5, OMIT];
var SETS =
[undefined, function set1() { return 1; }, function set2() { return 2; },
null, 5, OMIT];
var ENUMERABLES = [true, false, OMIT];
var CONFIGURABLES = [true, false, OMIT];
var WRITABLES = [true, false, OMIT];
function mapTestDescriptors(filter)
{
var descs = [];
var desc = {};
function put(field, value)
{
if (value !== OMIT)
desc[field] = value;
}
VALUES.forEach(function(value)
{
GETS.forEach(function(get)
{
SETS.forEach(function(set)
{
ENUMERABLES.forEach(function(enumerable)
{
CONFIGURABLES.forEach(function(configurable)
{
WRITABLES.forEach(function(writable)
{
desc = {};
put("value", value);
put("get", get);
put("set", set);
put("enumerable", enumerable);
put("configurable", configurable);
put("writable", writable);
if (filter(desc))
descs.push(desc);
});
});
});
});
});
});
return descs;
}
var ALL_DESCRIPTORS = mapTestDescriptors(function(d) { return true; });
var VALID_DESCRIPTORS = mapTestDescriptors(isValidDescriptor);
var SKIP_FULL_FUNCTION_LENGTH_TESTS = true;
function TestRunner()
{
this._logLines = [];
}
TestRunner.prototype =
{
// MAIN METHODS
runFunctionLengthTests: function runFunctionLengthTests()
{
var self = this;
function functionLengthTests()
{
if (SKIP_FULL_FUNCTION_LENGTH_TESTS)
{
print("Skipping full tests for redefining Function.length for now " +
"because we don't support redefinition of properties with " +
"native getter or setter...");
self._simpleFunctionLengthTests();
}
else
{
self._simpleFunctionLengthTests();
self._fullFunctionLengthTests(function() { }, 0);
self._fullFunctionLengthTests(function(one) { }, 1);
self._fullFunctionLengthTests(function(one, two) { }, 2);
}
}
this._runTestSet(functionLengthTests, "Function length tests completed!");
},
runNotPresentTests: function runNotPresentTests()
{
var self = this;
function notPresentTests()
{
print("Running not-present tests now...");
for (var i = 0, sz = ALL_DESCRIPTORS.length; i < sz; i++)
self._runSingleNotPresentTest(ALL_DESCRIPTORS[i]);
};
this._runTestSet(notPresentTests, "Not-present length tests completed!");
},
runPropertyPresentTestsFraction:
function runPropertyPresentTestsFraction(part, parts)
{
var self = this;
function propertyPresentTests()
{
print("Running already-present tests now...");
var total = VALID_DESCRIPTORS.length;
var start = Math.floor((part - 1) / parts * total);
var end = Math.floor(part / parts * total);
for (var i = start; i < end; i++)
{
var old = VALID_DESCRIPTORS[i];
print("Starting test with old descriptor " + old.toSource() + "...");
for (var j = 0, sz2 = VALID_DESCRIPTORS.length; j < sz2; j++)
self._runSinglePropertyPresentTest(old, VALID_DESCRIPTORS[j]);
}
}
this._runTestSet(propertyPresentTests,
"Property-present fraction " + part + " of " + parts +
" completed!");
},
// HELPERS
runPropertyPresentTests: function runPropertyPresentTests()
{
print("Running already-present tests now...");
for (var i = 0, sz = VALID_DESCRIPTORS.length; i < sz; i++)
{
var old = VALID_DESCRIPTORS[i];
print("Starting test with old descriptor " + old.toSource() + "...");
for (var j = 0, sz2 = VALID_DESCRIPTORS.length; j < sz2; j++)
this._runSinglePropertyPresentTest(old, VALID_DESCRIPTORS[j]);
}
},
_runTestSet: function _runTestSet(fun, completeMessage)
{
try
{
fun();
print(completeMessage);
}
catch (e)
{
print("ERROR, EXITING (line " + (e.lineNumber || -1) + "): " + e);
throw e;
}
finally
{
this._reportAllErrors();
}
},
_reportAllErrors: function _reportAllErrors()
{
var errorCount = this._logLines.length;
print("Full accumulated number of errors: " + errorCount);
if (errorCount > 0)
throw errorCount + " errors detected, FAIL";
},
_simpleFunctionLengthTests: function _simpleFunctionLengthTests(fun)
{
print("Running simple Function.length tests now..");
function expectThrowTypeError(o, p, desc)
{
var err = "<none>", passed = false;
try
{
Object.defineProperty(o, p, desc);
}
catch (e)
{
err = e;
passed = e instanceof TypeError;
}
assertEq(passed, true, fun + " didn't throw TypeError when called: " + err);
}
expectThrowTypeError(function a() { }, "length", { value: 1 });
expectThrowTypeError(function a() { }, "length", { enumerable: true });
expectThrowTypeError(function a() { }, "length", { configurable: true });
expectThrowTypeError(function a() { }, "length", { writable: true });
},
_fullFunctionLengthTests: function _fullFunctionLengthTests(fun)
{
var len = fun.length;
print("Running Function.length (" + len + ") tests now...");
var desc;
var gen = new DescriptorState();
while ((desc = gen.nextDescriptor()))
this._runSingleFunctionLengthTest(fun, len, desc);
},
_log: function _log(v)
{
var m = "" + v;
print(m);
this._logLines.push(m);
},
_runSingleNotPresentTest: function _runSingleNotPresentTest(desc)
{
var nativeObj = NativeTest.newObject();
var reimplObj = ReimplTest.newObject();
try
{
NativeTest.defineProperty(nativeObj, "foo", desc);
}
catch (e)
{
try
{
ReimplTest.defineProperty(reimplObj, "foo", desc);
}
catch (e)
{
return;
}
this._log("Difference when comparing native/reimplementation " +
"behavior for new descriptor " + desc.toSource() +
", error " + e);
return;
}
try
{
ReimplTest.defineProperty(reimplObj, "foo", desc);
}
catch (e)
{
this._log("Reimpl threw defining new descriptor " + desc.toSource() +
", error: " + e);
return;
}
var nativeDesc = NativeTest.getDescriptor(nativeObj, "foo");
var reimplDesc = ReimplTest.getDescriptor(reimplObj, "foo");
try
{
compareDescriptors(nativeDesc, reimplDesc);
}
catch (e)
{
this._log("Difference comparing returned descriptors for new " +
"property defined with descriptor " + desc.toSource() +
"; error: " + e);
return;
}
},
_runSinglePropertyPresentTest: function _runSinglePropertyPresentTest(old, add)
{
var nativeObj = NativeTest.newObject();
var reimplObj = ReimplTest.newObject();
try
{
NativeTest.defineProperty(nativeObj, "foo", old);
}
catch (e)
{
if (!SameValue(NativeTest.getDescriptor(nativeObj, "foo"),
undefined))
{
this._log("defining bad property descriptor: " + old.toSource());
return;
}
try
{
ReimplTest.defineProperty(reimplObj, "foo", old);
}
catch (e2)
{
if (!SameValue(ReimplTest.getDescriptor(reimplObj, "foo"),
undefined))
{
this._log("defining bad property descriptor: " + old.toSource() +
"; reimplObj: " + uneval(reimplObj));
}
return;
}
this._log("Difference defining a property with descriptor " +
old.toSource() + ", error " + e);
return;
}
try
{
ReimplTest.defineProperty(reimplObj, "foo", old);
}
catch (e)
{
this._log("Difference when comparing native/reimplementation " +
"behavior when adding descriptor " + add.toSource() +
", error: " + e);
return;
}
try
{
NativeTest.defineProperty(nativeObj, "foo", add);
}
catch (e)
{
try
{
ReimplTest.defineProperty(reimplObj, "foo", add);
}
catch (e2)
{
return;
}
this._log("Difference when comparing native/reimplementation " +
"behavior for added descriptor " + add.toSource() + ", " +
"initial was " + old.toSource() + "; error: " + e);
return;
}
try
{
ReimplTest.defineProperty(reimplObj, "foo", add);
}
catch (e)
{
this._log("Difference when comparing native/reimplementation " +
"behavior for readded descriptor " + add.toSource() + ", " +
"initial was " + old.toSource() + "; native readd didn't " +
"throw, reimpl add did, error: " + e);
return;
}
var nativeDesc = NativeTest.getDescriptor(nativeObj, "foo");
var reimplDesc = ReimplTest.getDescriptor(reimplObj, "foo");
try
{
compareDescriptors(nativeDesc, reimplDesc);
}
catch (e)
{
this._log("Difference comparing returned descriptors for readded " +
"property defined with descriptor " + add.toSource() + "; " +
"initial was " + old.toSource() + "; error: " + e);
return;
}
},
_runSingleFunctionLengthTest: function _runSingleFunctionLengthTest(fun, len, desc)
{
var nativeObj = fun;
var reimplObj = ReimplTest.newObject();
ReimplTest.defineProperty(reimplObj, "length",
{
value: len,
enumerable: false,
configurable: false,
writable: false
});
try
{
NativeTest.defineProperty(nativeObj, "length", desc);
}
catch (e)
{
try
{
ReimplTest.defineProperty(reimplObj, "length", desc);
}
catch (e)
{
return;
}
this._log("Difference when comparing Function.length native/reimpl " +
"behavior for descriptor " + desc.toSource() +
", native impl threw error " + e);
return;
}
try
{
ReimplTest.defineProperty(reimplObj, "length", desc);
}
catch (e)
{
this._log("Difference defining new Function.length descriptor: impl " +
"succeeded, reimpl threw for descriptor " +
desc.toSource() + ", error: " + e);
return;
}
var nativeDesc = NativeTest.getDescriptor(nativeObj, "length");
var reimplDesc = ReimplTest.getDescriptor(reimplObj, "length");
try
{
compareDescriptors(nativeDesc, reimplDesc);
}
catch (e)
{
this._log("Difference comparing returned descriptors for " +
"Function.length with descriptor " + desc.toSource() +
"; error: " + e);
return;
}
}
};

View File

@ -1,3 +1,11 @@
url-prefix ../../jsreftest.html?test=ecma_5/Object/
script 15.2.3.3-01.js # does not use reportCompare
script 15.2.3.6-function-length.js
script 15.2.3.6-miscellaneous.js
script 15.2.3.6-new-definition.js
skip-if(!xulRuntime.shell) script 15.2.3.6-redefinition-1-of-4.js # uses shell load() function
skip-if(!xulRuntime.shell) script 15.2.3.6-redefinition-2-of-4.js # uses shell load() function
skip-if(!xulRuntime.shell) script 15.2.3.6-redefinition-3-of-4.js # uses shell load() function
skip-if(!xulRuntime.shell) script 15.2.3.6-redefinition-4-of-4.js # uses shell load() function
script 15.2.3.7-01.js
script 15.2.3.14-01.js # does not use reportCompare