From 285792a4b488ea9b2a5e2dbcac97aa2661579e01 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 5 Jun 2009 12:56:45 -0700 Subject: [PATCH] Bug 430133 - Implement ES3.1's Object.defineProperty and Object.defineProperties. r=jorendorff --- js/src/js.msg | 6 + js/src/jsobj.cpp | 713 +++++++++++++- js/src/jsobj.h | 81 +- js/src/json.cpp | 9 +- js/src/jsprvtd.h | 6 + js/src/jsscope.cpp | 5 +- js/src/jsscope.h | 39 +- .../ecma_5/Object/15.2.3.6-function-length.js | 35 + .../ecma_5/Object/15.2.3.6-miscellaneous.js | 65 ++ .../ecma_5/Object/15.2.3.6-new-definition.js | 35 + .../Object/15.2.3.6-redefinition-1-of-4.js | 39 + .../Object/15.2.3.6-redefinition-2-of-4.js | 39 + .../Object/15.2.3.6-redefinition-3-of-4.js | 39 + .../Object/15.2.3.6-redefinition-4-of-4.js | 39 + js/src/tests/ecma_5/Object/15.2.3.7-01.js | 83 ++ .../ecma_5/Object/defineProperty-setup.js | 876 ++++++++++++++++++ js/src/tests/ecma_5/Object/jstests.list | 8 + 17 files changed, 2041 insertions(+), 76 deletions(-) create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-function-length.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-miscellaneous.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-new-definition.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-redefinition-1-of-4.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-redefinition-2-of-4.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-redefinition-3-of-4.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.6-redefinition-4-of-4.js create mode 100644 js/src/tests/ecma_5/Object/15.2.3.7-01.js create mode 100644 js/src/tests/ecma_5/Object/defineProperty-setup.js diff --git a/js/src/js.msg b/js/src/js.msg index 40fa9ee2cf7..879fa54f171 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -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") diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 91b0dd01daa..609ae7571b0 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -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(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 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(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, ¤t)) + 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(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 }; diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 70623692c6a..ae3079009ef 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -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); diff --git a/js/src/json.cpp b/js/src/json.cpp index 3d0372965d5..47f15111dcc 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -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; diff --git a/js/src/jsprvtd.h b/js/src/jsprvtd.h index 8ce4784f155..d1911af612b 100644 --- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -159,6 +159,12 @@ class Vector; /* Common instantiations. */ typedef js::Vector JSCharBuffer; +static inline JSPropertyOp +js_CastAsPropertyOp(JSObject *object) +{ + return JS_DATA_TO_FUNC_PTR(JSPropertyOp, object); +} + } /* export "C++" */ #endif /* __cplusplus */ diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 316087ea1ec..5d5f06c1ef5 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -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); } diff --git a/js/src/jsscope.h b/js/src/jsscope.h index 57d52840c4c..c49347b09ff 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -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. */ diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-function-length.js b/js/src/tests/ecma_5/Object/15.2.3.6-function-length.js new file mode 100644 index 00000000000..3cfae6a83f2 --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-function-length.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-miscellaneous.js b/js/src/tests/ecma_5/Object/15.2.3.6-miscellaneous.js new file mode 100644 index 00000000000..61e076b905d --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-miscellaneous.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-new-definition.js b/js/src/tests/ecma_5/Object/15.2.3.6-new-definition.js new file mode 100644 index 00000000000..5b8b29ed94f --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-new-definition.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-1-of-4.js b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-1-of-4.js new file mode 100644 index 00000000000..257a1e1b39a --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-1-of-4.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-2-of-4.js b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-2-of-4.js new file mode 100644 index 00000000000..1fb4478f7a3 --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-2-of-4.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-3-of-4.js b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-3-of-4.js new file mode 100644 index 00000000000..d3ffba20823 --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-3-of-4.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-4-of-4.js b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-4-of-4.js new file mode 100644 index 00000000000..ae94e0f0fc6 --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.6-redefinition-4-of-4.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/15.2.3.7-01.js b/js/src/tests/ecma_5/Object/15.2.3.7-01.js new file mode 100644 index 00000000000..4c553cd3d6d --- /dev/null +++ b/js/src/tests/ecma_5/Object/15.2.3.7-01.js @@ -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!"); diff --git a/js/src/tests/ecma_5/Object/defineProperty-setup.js b/js/src/tests/ecma_5/Object/defineProperty-setup.js new file mode 100644 index 00000000000..030a997e031 --- /dev/null +++ b/js/src/tests/ecma_5/Object/defineProperty-setup.js @@ -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 = "", 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; + } + } + }; diff --git a/js/src/tests/ecma_5/Object/jstests.list b/js/src/tests/ecma_5/Object/jstests.list index f400a67773e..2b1b7f3d109 100644 --- a/js/src/tests/ecma_5/Object/jstests.list +++ b/js/src/tests/ecma_5/Object/jstests.list @@ -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