diff --git a/js/src/js.msg b/js/src/js.msg index a01283ad6e45..76975aca334d 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -100,7 +100,7 @@ MSG_DEF(JSMSG_NEED_DIET, 17, 1, JSEXN_INTERNALERR, "{0} too large" MSG_DEF(JSMSG_TOO_MANY_LOCAL_ROOTS, 18, 0, JSEXN_ERR, "out of local root space") MSG_DEF(JSMSG_READ_ONLY, 19, 1, JSEXN_TYPEERR, "{0} is read-only") MSG_DEF(JSMSG_BAD_FORMAL, 20, 0, JSEXN_SYNTAXERR, "malformed formal parameter") -MSG_DEF(JSMSG_CANT_DELETE, 21, 1, JSEXN_TYPEERR, "property '{0}' is non-configurable and cannot be deleted") +MSG_DEF(JSMSG_CANT_DELETE, 21, 1, JSEXN_TYPEERR, "property {0} is non-configurable and can't be deleted") MSG_DEF(JSMSG_NOT_FUNCTION, 22, 1, JSEXN_TYPEERR, "{0} is not a function") MSG_DEF(JSMSG_NOT_CONSTRUCTOR, 23, 1, JSEXN_TYPEERR, "{0} is not a constructor") MSG_DEF(JSMSG_SCRIPT_STACK_QUOTA, 24, 0, JSEXN_INTERNALERR, "script stack space quota is exhausted") @@ -116,7 +116,7 @@ MSG_DEF(JSMSG_BAD_RADIX, 33, 1, JSEXN_ERR, "illegal radix {0}") MSG_DEF(JSMSG_PAREN_BEFORE_LET, 34, 0, JSEXN_SYNTAXERR, "missing ( before let head") MSG_DEF(JSMSG_CANT_CONVERT, 35, 1, JSEXN_ERR, "can't convert {0} to an integer") MSG_DEF(JSMSG_CYCLIC_VALUE, 36, 1, JSEXN_TYPEERR, "cyclic {0} value") -MSG_DEF(JSMSG_COMPILE_EXECED_SCRIPT, 37, 0, JSEXN_TYPEERR, "cannot compile over a script that is currently executing") +MSG_DEF(JSMSG_COMPILE_EXECED_SCRIPT, 37, 0, JSEXN_TYPEERR, "can't compile over a script that is currently executing") MSG_DEF(JSMSG_CANT_CONVERT_TO, 38, 2, JSEXN_TYPEERR, "can't convert {0} to {1}") MSG_DEF(JSMSG_NO_PROPERTIES, 39, 1, JSEXN_TYPEERR, "{0} has no properties") MSG_DEF(JSMSG_CANT_FIND_CLASS, 40, 1, JSEXN_TYPEERR, "can't find class id {0}") @@ -158,7 +158,7 @@ MSG_DEF(JSMSG_PAREN_AFTER_COND, 75, 0, JSEXN_SYNTAXERR, "missing ) after MSG_DEF(JSMSG_DESTRUCT_DUP_ARG, 76, 0, JSEXN_SYNTAXERR, "duplicate argument is mixed with destructuring pattern") MSG_DEF(JSMSG_NAME_AFTER_DOT, 77, 0, JSEXN_SYNTAXERR, "missing name after . operator") MSG_DEF(JSMSG_BRACKET_IN_INDEX, 78, 0, JSEXN_SYNTAXERR, "missing ] in index expression") -MSG_DEF(JSMSG_XML_WHOLE_PROGRAM, 79, 0, JSEXN_SYNTAXERR, "XML cannot be the whole program") +MSG_DEF(JSMSG_XML_WHOLE_PROGRAM, 79, 0, JSEXN_SYNTAXERR, "XML can't be the whole program") MSG_DEF(JSMSG_PAREN_BEFORE_SWITCH, 80, 0, JSEXN_SYNTAXERR, "missing ( before switch expression") MSG_DEF(JSMSG_PAREN_AFTER_SWITCH, 81, 0, JSEXN_SYNTAXERR, "missing ) after switch expression") MSG_DEF(JSMSG_CURLY_BEFORE_SWITCH, 82, 0, JSEXN_SYNTAXERR, "missing { before switch body") @@ -192,7 +192,7 @@ MSG_DEF(JSMSG_SEMI_BEFORE_STMNT, 109, 0, JSEXN_SYNTAXERR, "missing ; before MSG_DEF(JSMSG_NO_RETURN_VALUE, 110, 1, JSEXN_TYPEERR, "function {0} does not always return a value") MSG_DEF(JSMSG_DUPLICATE_FORMAL, 111, 1, JSEXN_SYNTAXERR, "duplicate formal argument {0}") MSG_DEF(JSMSG_EQUAL_AS_ASSIGN, 112, 1, JSEXN_SYNTAXERR, "test for equality (==) mistyped as assignment (=)?{0}") -MSG_DEF(JSMSG_OPTIMIZED_CLOSURE_LEAK, 113, 0, JSEXN_INTERNALERR, "cannot access optimized closure") +MSG_DEF(JSMSG_OPTIMIZED_CLOSURE_LEAK, 113, 0, JSEXN_INTERNALERR, "can't access optimized closure") MSG_DEF(JSMSG_TOO_MANY_DEFAULTS, 114, 0, JSEXN_SYNTAXERR, "more than one switch default") MSG_DEF(JSMSG_TOO_MANY_CASES, 115, 0, JSEXN_INTERNALERR, "too many switch cases") MSG_DEF(JSMSG_BAD_SWITCH, 116, 0, JSEXN_SYNTAXERR, "invalid switch statement") @@ -298,7 +298,7 @@ MSG_DEF(JSMSG_BAD_GENERATOR_SYNTAX, 215, 1, JSEXN_SYNTAXERR, "{0} expression m MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 216, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side") MSG_DEF(JSMSG_NON_XML_FILTER, 217, 1, JSEXN_TYPEERR, "XML filter is applied to non-XML value {0}") MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 218, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value") -MSG_DEF(JSMSG_NON_LIST_XML_METHOD, 219, 2, JSEXN_TYPEERR, "cannot call {0} method on an XML list with {1} elements") +MSG_DEF(JSMSG_NON_LIST_XML_METHOD, 219, 2, JSEXN_TYPEERR, "can't call {0} method on an XML list with {1} elements") MSG_DEF(JSMSG_BAD_DELETE_OPERAND, 220, 0, JSEXN_SYNTAXERR, "invalid delete operand") MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 221, 0, JSEXN_SYNTAXERR, "invalid increment/decrement operand") MSG_DEF(JSMSG_UNEXPECTED_TYPE, 222, 2, JSEXN_TYPEERR, "{0} is {1}") @@ -318,10 +318,10 @@ MSG_DEF(JSMSG_DEPRECATED_DELETE_OPERAND, 235, 0, JSEXN_SYNTAXERR, "Applying the 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_OBJECT_NOT_EXTENSIBLE, 239, 1, JSEXN_TYPEERR, "{0} is not extensible") +MSG_DEF(JSMSG_CANT_REDEFINE_PROP, 240, 1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'") +MSG_DEF(JSMSG_CANT_APPEND_TO_ARRAY, 241, 0, JSEXN_TYPEERR, "can't add elements past the end of an array if its length property is unwritable") +MSG_DEF(JSMSG_CANT_DEFINE_ARRAY_LENGTH,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") MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX, 244, 0, JSEXN_ERR, "invalid or out-of-range index") MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 245, 1, JSEXN_ERR, "argument {0} must be >= 0") diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index b7bfa6e24c80..754b973bfd97 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -152,7 +152,7 @@ obj_setProto(JSContext *cx, JSObject *obj, jsid id, Value *vp) { /* ECMAScript 5 8.6.2 forbids changing [[Prototype]] if not [[Extensible]]. */ if (!obj->isExtensible()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OBJECT_NOT_EXTENSIBLE); + obj->reportNotExtensible(cx); return false; } @@ -1971,10 +1971,17 @@ Reject(JSContext *cx, uintN errorNumber, bool throwError, jsid id, bool *rval) } static JSBool -Reject(JSContext *cx, uintN errorNumber, bool throwError, bool *rval) +Reject(JSContext *cx, JSObject *obj, uintN errorNumber, bool throwError, bool *rval) { if (throwError) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber); + if (js_ErrorFormatString[errorNumber].argCount == 1) { + js_ReportValueErrorFlags(cx, JSREPORT_ERROR, errorNumber, + JSDVG_IGNORE_STACK, ObjectValue(*obj), + NULL, NULL, NULL); + } else { + JS_ASSERT(js_ErrorFormatString[errorNumber].argCount == 0); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber); + } return JS_FALSE; } @@ -2006,7 +2013,7 @@ DefinePropertyOnObject(JSContext *cx, JSObject *obj, const PropDesc &desc, /* 8.12.9 steps 2-4. */ if (!current) { if (!obj->isExtensible()) - return Reject(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); + return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); *rval = true; @@ -2094,8 +2101,8 @@ DefinePropertyOnObject(JSContext *cx, JSObject *obj, const PropDesc &desc, */ if (!shape->configurable() && (!shape->hasDefaultGetter() || !shape->hasDefaultSetter())) { - return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, - throwError, desc.id, rval); + return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_PROP, throwError, + desc.id, rval); } if (!js_NativeGet(cx, obj, obj2, shape, JSGET_NO_METHOD_BARRIER, &v)) { @@ -2141,8 +2148,7 @@ DefinePropertyOnObject(JSContext *cx, JSObject *obj, const PropDesc &desc, JS_ASSERT_IF(!desc.hasConfigurable, !desc.configurable()); if (desc.configurable() || (desc.hasEnumerable && desc.enumerable() != shape->enumerable())) { - return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, throwError, - desc.id, rval); + return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_PROP, throwError, desc.id, rval); } } @@ -2150,18 +2156,16 @@ DefinePropertyOnObject(JSContext *cx, JSObject *obj, const PropDesc &desc, /* 8.12.9 step 8, no validation required */ } else if (desc.isDataDescriptor() != shape->isDataDescriptor()) { /* 8.12.9 step 9. */ - if (!shape->configurable()) { - return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, - throwError, desc.id, rval); - } + if (!shape->configurable()) + return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_PROP, throwError, desc.id, rval); } else if (desc.isDataDescriptor()) { /* 8.12.9 step 10. */ JS_ASSERT(shape->isDataDescriptor()); if (!shape->configurable() && !shape->writable()) { if ((desc.hasWritable && desc.writable()) || (desc.hasValue && !SameValue(desc.value, v, cx))) { - return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, - throwError, desc.id, rval); + return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_PROP, throwError, desc.id, + rval); } } } else { @@ -2172,8 +2176,8 @@ DefinePropertyOnObject(JSContext *cx, JSObject *obj, const PropDesc &desc, !SameValue(desc.setterValue(), shape->setterOrUndefined(), cx)) || (desc.hasGet && !SameValue(desc.getterValue(), shape->getterOrUndefined(), cx))) { - return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, - throwError, desc.id, rval); + return Reject(cx, obj2, current, JSMSG_CANT_REDEFINE_PROP, throwError, desc.id, + rval); } } } @@ -2281,7 +2285,7 @@ DefinePropertyOnArray(JSContext *cx, JSObject *obj, const PropDesc &desc, * 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); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_DEFINE_ARRAY_LENGTH); return JS_FALSE; } @@ -2290,12 +2294,12 @@ DefinePropertyOnArray(JSContext *cx, JSObject *obj, const PropDesc &desc, /* // Disabled until we support defining "length": if (index >= oldLen && lengthPropertyNotWritable()) - return ThrowTypeError(cx, JSMSG_CANT_APPEND_PROPERTIES_TO_UNWRITABLE_LENGTH_ARRAY); + return ThrowTypeError(cx, JSMSG_CANT_APPEND_TO_ARRAY); */ if (!DefinePropertyOnObject(cx, obj, desc, false, rval)) return JS_FALSE; if (!*rval) - return Reject(cx, JSMSG_CANT_DEFINE_ARRAY_INDEX, throwError, rval); + return Reject(cx, obj, JSMSG_CANT_DEFINE_ARRAY_INDEX, throwError, rval); if (index >= oldLen) { JS_ASSERT(index != UINT32_MAX); @@ -2319,7 +2323,7 @@ DefineProperty(JSContext *cx, JSObject *obj, const PropDesc &desc, bool throwErr if (obj->getOps()->lookupProperty) { if (obj->isProxy()) return JSProxy::defineProperty(cx, obj, desc.id, desc.pd); - return Reject(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); + return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval); } return DefinePropertyOnObject(cx, obj, desc, throwError, rval); @@ -5101,31 +5105,28 @@ js_CheckUndeclaredVarAssignment(JSContext *cx, JSString *propname) JSMSG_UNDECLARED_VAR, bytes); } -namespace js { - -JSBool -ReportReadOnly(JSContext* cx, jsid id, uintN flags) +bool +JSObject::reportReadOnly(JSContext* cx, jsid id, uintN report) { - return js_ReportValueErrorFlags(cx, flags, JSMSG_READ_ONLY, + return js_ReportValueErrorFlags(cx, report, JSMSG_READ_ONLY, JSDVG_IGNORE_STACK, IdToValue(id), NULL, NULL, NULL); } -JSBool -ReportNotConfigurable(JSContext* cx, jsid id, uintN flags) +bool +JSObject::reportNotConfigurable(JSContext* cx, jsid id, uintN report) { - return js_ReportValueErrorFlags(cx, flags, JSMSG_CANT_DELETE, + return js_ReportValueErrorFlags(cx, report, JSMSG_CANT_DELETE, JSDVG_IGNORE_STACK, IdToValue(id), NULL, NULL, NULL); } -JSBool -ReportNotExtensible(JSContext* cx, uintN flags) +bool +JSObject::reportNotExtensible(JSContext *cx, uintN report) { - return JS_ReportErrorFlagsAndNumber(cx, flags, js_GetErrorMessage, NULL, - JSMSG_OBJECT_NOT_EXTENSIBLE); -} - + return js_ReportValueErrorFlags(cx, report, JSMSG_OBJECT_NOT_EXTENSIBLE, + JSDVG_IGNORE_STACK, ObjectValue(*this), + NULL, NULL, NULL); } /* @@ -5212,9 +5213,9 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, /* Error in strict mode code, warn with strict option, otherwise do nothing. */ if (strict) - return ReportReadOnly(cx, id, 0); + return obj->reportReadOnly(cx, id); if (JS_HAS_STRICT_OPTION(cx)) - return ReportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); + return obj->reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); return JS_TRUE; #ifdef JS_TRACER @@ -5300,11 +5301,10 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, if (!obj->isExtensible()) { /* Error in strict mode code, warn with strict option, otherwise do nothing. */ if (strict) - return ReportNotExtensible(cx, 0); + return obj->reportNotExtensible(cx); if (JS_HAS_STRICT_OPTION(cx)) - return ReportNotExtensible(cx, JSREPORT_STRICT | JSREPORT_WARNING); - else - return JS_TRUE; + return obj->reportNotExtensible(cx, JSREPORT_STRICT | JSREPORT_WARNING); + return JS_TRUE; } /* @@ -5451,7 +5451,7 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool str if (shape->isSharedPermanent()) { JS_UNLOCK_OBJ(cx, proto); if (strict) - return ReportNotConfigurable(cx, id, 0); + return obj->reportNotConfigurable(cx, id); rval->setBoolean(false); return true; } @@ -5470,7 +5470,7 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool str if (!shape->configurable()) { JS_UNLOCK_OBJ(cx, obj); if (strict) - return ReportNotConfigurable(cx, id, 0); + return obj->reportNotConfigurable(cx, id); rval->setBoolean(false); return true; } diff --git a/js/src/jsobj.h b/js/src/jsobj.h index d03e68435afc..a104529c7426 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1005,16 +1005,25 @@ struct JSObject { bool allocSlot(JSContext *cx, uint32 *slotp); void freeSlot(JSContext *cx, uint32 slot); - private: - void reportReadOnlyScope(JSContext *cx); + bool reportReadOnly(JSContext* cx, jsid id, uintN report = JSREPORT_ERROR); + bool reportNotConfigurable(JSContext* cx, jsid id, uintN report = JSREPORT_ERROR); + bool reportNotExtensible(JSContext *cx, uintN report = JSREPORT_ERROR); + private: js::Shape *getChildProperty(JSContext *cx, js::Shape *parent, js::Shape &child); - const js::Shape *addPropertyCommon(JSContext *cx, jsid id, - js::PropertyOp getter, js::PropertyOp setter, - uint32 slot, uintN attrs, - uintN flags, intN shortid, - js::Shape **spp); + /* + * Internal helper that adds a shape not yet mapped by this object. + * + * Notes: + * 1. getter and setter must be normalized based on flags (see jsscope.cpp). + * 2. !isExtensible() checking must be done by callers. + */ + const js::Shape *addPropertyInternal(JSContext *cx, jsid id, + js::PropertyOp getter, js::PropertyOp setter, + uint32 slot, uintN attrs, + uintN flags, intN shortid, + js::Shape **spp); bool toDictionaryMode(JSContext *cx); @@ -1041,7 +1050,7 @@ struct JSObject { const js::Shape *changeProperty(JSContext *cx, const js::Shape *shape, uintN attrs, uintN mask, js::PropertyOp getter, js::PropertyOp setter); - /* Remove id from this object. */ + /* Remove the property named by id from this object. */ bool removeProperty(JSContext *cx, jsid id); /* Clear the scope, making it empty. */ diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 836e2546899c..347a330a361f 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -513,21 +513,6 @@ JSObject::getChildProperty(JSContext *cx, Shape *parent, Shape &child) return shape; } -void -JSObject::reportReadOnlyScope(JSContext *cx) -{ - JSString *str; - const char *bytes; - - str = js_ValueToString(cx, ObjectValue(*this)); - if (!str) - return; - bytes = js_GetStringBytes(cx, str); - if (!bytes) - return; - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_READ_ONLY, bytes); -} - Shape * Shape::newDictionaryShape(JSContext *cx, const Shape &child, Shape **listp) { @@ -720,29 +705,27 @@ JSObject::addProperty(JSContext *cx, jsid id, { JS_ASSERT(!JSID_IS_VOID(id)); + if (!isExtensible()) { + reportNotExtensible(cx); + return NULL; + } + NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter); /* Search for id with adding = true in order to claim its entry. */ Shape **spp = nativeSearch(id, true); JS_ASSERT(!SHAPE_FETCH(spp)); - return addPropertyCommon(cx, id, getter, setter, slot, attrs, flags, shortid, spp); + return addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp); } const Shape * -JSObject::addPropertyCommon(JSContext *cx, jsid id, - PropertyOp getter, PropertyOp setter, - uint32 slot, uintN attrs, - uintN flags, intN shortid, - Shape **spp) +JSObject::addPropertyInternal(JSContext *cx, jsid id, + PropertyOp getter, PropertyOp setter, + uint32 slot, uintN attrs, + uintN flags, intN shortid, + Shape **spp) { - /* - * You can't add properties to a non-extensible object, but you can change - * attributes of properties in such objects. - */ - if (!isExtensible()) { - reportReadOnlyScope(cx); - return NULL; - } + JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen()); PropertyTable *table = NULL; if (!inDictionaryMode()) { @@ -822,137 +805,182 @@ JSObject::putProperty(JSContext *cx, jsid id, uint32 slot, uintN attrs, uintN flags, intN shortid) { - Shape **spp, *shape, *overwriting; - JS_ASSERT(!JSID_IS_VOID(id)); + /* + * Horrid non-strict eval, debuggers, and |default xml namespace ...| may + * extend Call objects. + */ + if (lastProp->frozen()) { + if (!Shape::newDictionaryList(cx, &lastProp)) + return NULL; + JS_ASSERT(!lastProp->frozen()); + } + NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter); /* Search for id in order to claim its entry if table has been allocated. */ - spp = nativeSearch(id, true); - shape = SHAPE_FETCH(spp); - if (!shape) - return addPropertyCommon(cx, id, getter, setter, slot, attrs, flags, shortid, spp); + Shape **spp = nativeSearch(id, true); + Shape *shape = SHAPE_FETCH(spp); + if (!shape) { + /* + * You can't add properties to a non-extensible object, but you can change + * attributes of properties in such objects. + */ + if (!isExtensible()) { + reportNotExtensible(cx); + return NULL; + } + + return addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp); + } /* Property exists: search must have returned a valid *spp. */ JS_ASSERT(!SHAPE_IS_REMOVED(*spp)); - overwriting = shape; /* - * If all property members match, this is a redundant add and we can - * return early. If the caller wants to allocate a slot, but doesn't - * care which slot, copy shape->slot into slot so we can match shape, - * if all other members match. + * If the caller wants to allocate a slot, but doesn't care which slot, + * copy the existing shape's slot into slot so we can match shape, if all + * other members match. */ - bool hadSlot = !shape->isAlias() && containsSlot(shape->slot); + bool hadSlot = !shape->isAlias() && shape->hasSlot(); uint32 oldSlot = shape->slot; if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot) slot = oldSlot; + + /* + * Now that we've possibly preserved slot, check whether all members match. + * If so, this is a redundant "put" and we can return without more work. + */ if (shape->matchesParamsAfterId(getter, setter, slot, attrs, flags, shortid)) { METER(redundantPuts); return shape; } - PropertyTable *table = inDictionaryMode() ? lastProp->table : NULL; + /* + * Overwriting a non-last property requires switching to dictionary mode. + * The shape tree is shared immutable, and we can't removeProperty and then + * addPropertyInternal because a failure under add would lose data. + */ + if (shape != lastProp && !inDictionaryMode()) { + if (!toDictionaryMode(cx)) + return false; + spp = nativeSearch(shape->id); + shape = SHAPE_FETCH(spp); + } /* - * If we are clearing shape to force the existing property that it - * describes to be overwritten, then we have to unlink shape from the - * ancestor line at lastProp->lastProp. + * Now that we have passed the lastProp->frozen() check at the top of this + * method, and the non-last-property conditioning just above, we are ready + * to overwrite. * - * If shape is not lastProp and this scope is not in dictionary mode, - * we must switch to dictionary mode so we can unlink the non-terminal - * shape without breaking anyone sharing the property lineage via our - * prototype's property tree. + * Optimize the case of a non-frozen dictionary-mode object based on the + * property that dictionaries exclusively own their mutable shape structs, + * each of which has a unique shape number (not shared via a shape tree). */ - Shape *oldLastProp = lastProp; - if (shape == lastProp && !inDictionaryMode()) { - removeLastProperty(); - } else { - if (!inDictionaryMode()) { - if (!toDictionaryMode(cx)) + if (inDictionaryMode()) { + /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */ + if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED) && !(flags & Shape::ALIAS)) { + if (!allocSlot(cx, &slot)) return NULL; - - spp = nativeSearch(id); - shape = SHAPE_FETCH(spp); - table = lastProp->table; - oldLastProp = lastProp; - } - shape->removeFromDictionary(this); - } - -#ifdef DEBUG - if (shape == oldLastProp) { - JS_ASSERT(lastProp->slotSpan <= shape->slotSpan); - if (shape->hasSlot()) - JS_ASSERT(shape->slot < shape->slotSpan); - if (lastProp->slotSpan < numSlots()) - getSlotRef(lastProp->slotSpan).setUndefined(); - } -#endif - - /* - * If we fail later on trying to find or create a new shape, we will - * restore *spp from |overwriting|. Note that we don't bother to keep - * table->removedCount in sync, because we will fix up both *spp and - * table->entryCount shortly. - */ - if (table) - SHAPE_STORE_PRESERVING_COLLISION(spp, NULL); - - { - /* Find or create a property tree node labeled by our arguments. */ - Shape child(id, getter, setter, slot, attrs, flags, shortid); - shape = getChildProperty(cx, lastProp, child); - } - - if (shape) { - JS_ASSERT(shape == lastProp); - - if (table) { - /* Store the tree node pointer in the table entry for id. */ - SHAPE_STORE_PRESERVING_COLLISION(spp, shape); - - /* Move table from oldLastProp to the new lastProp, aka shape. */ - JS_ASSERT(oldLastProp->table == table); - oldLastProp->setTable(NULL); - shape->setTable(table); - } - - if (!lastProp->table) { - /* See comment in JSObject::addPropertyCommon about ignoring OOM here. */ - lastProp->maybeHash(cx); } /* - * Can't fail now, so free the previous incarnation's slot if the new - * shape has no slot. But we do not need to free oldSlot (and must not, - * as trying to will botch an assertion in JSObject::freeSlot) if the - * new lastProp (shape here) has a slotSpan that does not cover it. + * We are going to mutate shape and move it to be lastProp if it isn't + * already, so we must regenerate its shape. */ - if (hadSlot && !shape->hasSlot() && oldSlot < shape->slotSpan) { - freeSlot(cx, oldSlot); - JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals); + shape->shape = js_GenerateShape(cx, false); + + /* + * Set shape->slot before calling shape->insertIntoDictionary, which + * uses it under shape->setParent. + */ + shape->slot = slot; + + if (shape != lastProp) { + if (PropertyTable *table = lastProp->table) { + shape->table = table; + lastProp->table = NULL; + } + shape->removeFromDictionary(this); + shape->insertIntoDictionary(&lastProp); + } else { + if (slot != SHAPE_INVALID_SLOT && slot >= shape->slotSpan) + shape->slotSpan = slot + 1; } - CHECK_SHAPE_CONSISTENCY(this); - METER(puts); - return shape; + shape->rawGetter = getter; + shape->rawSetter = setter; + shape->attrs = attrs; + shape->flags = flags | Shape::IN_DICTIONARY; + shape->shortid = shortid; + + /* + * We are done updating shape and lastProp. Now we may need to update + * flags and objShape. In the last non-dictionary property case in the + * else clause just below, getChildProperty handles this for us. + */ + updateFlags(shape); + updateShape(cx); + } else { + /* + * Updating lastProp in a non-dictionary-mode object. Such objects + * share their shapes via a tree rooted at a prototype emptyShape, or + * perhaps a well-known compartment-wide singleton emptyShape. + * + * If any shape in the tree has a property hashtable, it is shared and + * immutable too, therefore we must not update *spp. + */ + JS_ASSERT(shape == lastProp); + removeLastProperty(); + + /* Find or create a property tree node labeled by our arguments. */ + Shape child(id, getter, setter, slot, attrs, flags, shortid); + + Shape *newShape = getChildProperty(cx, lastProp, child); + if (!newShape) { + setLastProperty(shape); + CHECK_SHAPE_CONSISTENCY(this); + METER(putFails); + return NULL; + } + + shape = newShape; + } + + JS_ASSERT(shape == lastProp); + + if (!shape->table) { + /* See JSObject::addPropertyInternal comment about ignoring OOM. */ + shape->maybeHash(cx); + } + + /* + * Can't fail now, so free the previous incarnation's slot if the new shape + * has no slot. But we do not need to free oldSlot (and must not, as trying + * to will botch an assertion in JSObject::freeSlot) if the new lastProp + * (shape here) has a slotSpan that does not cover it. + */ + if (hadSlot && !shape->hasSlot()) { + if (oldSlot < shape->slotSpan) + freeSlot(cx, oldSlot); +#ifdef DEBUG + else + getSlotRef(oldSlot).setUndefined(); +#endif + JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals); } - if (table) - SHAPE_STORE_PRESERVING_COLLISION(spp, overwriting); CHECK_SHAPE_CONSISTENCY(this); - METER(putFails); - return NULL; + METER(puts); + return shape; } const Shape * JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN mask, PropertyOp getter, PropertyOp setter) { - const Shape *newShape; - + JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen()); JS_ASSERT(!JSID_IS_VOID(shape->id)); JS_ASSERT(nativeContains(*shape)); @@ -974,6 +1002,8 @@ JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN m Shape child(shape->id, getter, setter, shape->slot, attrs, shape->flags, shape->shortid); + const Shape *newShape; + if (inDictionaryMode()) { shape->removeFromDictionary(this); newShape = Shape::newDictionaryShape(cx, child, &lastProp); @@ -1014,11 +1044,15 @@ JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN m /* * Let JSObject::putProperty handle this |overwriting| case, including * the conservation of shape->slot (if it's valid). We must not call - * JSObject::removeProperty because it will free a valid shape->slot and - * JSObject::putProperty won't re-allocate it. + * removeProperty because it will free an allocated shape->slot, and + * putProperty won't re-allocate it. */ newShape = putProperty(cx, child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs, child.flags, child.shortid); +#ifdef DEBUG + if (newShape) + METER(changePuts); +#endif } #ifdef DEBUG @@ -1041,27 +1075,26 @@ JSObject::removeProperty(JSContext *cx, jsid id) return true; } - /* If shape is not the last property added, switch to dictionary mode. */ - if (shape != lastProp) { - if (!inDictionaryMode()) { - if (!toDictionaryMode(cx)) - return false; - spp = nativeSearch(shape->id); - shape = SHAPE_FETCH(spp); - } - JS_ASSERT(SHAPE_FETCH(spp) == shape); - } - - /* First, if shape is unshared and not cleared, free its slot number. */ - bool hadSlot = !shape->isAlias() && containsSlot(shape->slot); + /* First, if shape is unshared and not has a slot, free its slot number. */ + bool hadSlot = !shape->isAlias() && shape->hasSlot(); if (hadSlot) { freeSlot(cx, shape->slot); JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals); } + + /* If shape is not the last property added, switch to dictionary mode. */ + if (shape != lastProp && !inDictionaryMode()) { + if (!toDictionaryMode(cx)) + return false; + spp = nativeSearch(shape->id); + shape = SHAPE_FETCH(spp); + } + /* - * Next, consider removing id from lastProp->table if in dictionary mode, - * by setting its entry to a removed or free sentinel. + * A dictionary-mode object owns mutable, unique shapes on a non-circular + * doubly linked list, optionally hashed by lastProp->table. So we can edit + * the list and hash in place. */ if (inDictionaryMode()) { PropertyTable *table = lastProp->table; @@ -1092,12 +1125,13 @@ JSObject::removeProperty(JSContext *cx, jsid id) /* * Remove shape from its non-circular doubly linked list, setting this - * object's shape first so the updateShape(cx) after this if-else will - * generate a fresh shape for this scope. We need a fresh shape for all - * deletions, even of lastProp. Otherwise, a shape number can replay - * and caches may return get deleted DictionaryShapes! See bug 595365. + * object's OWN_SHAPE flag so the updateShape(cx) further below will + * generate a fresh shape id for this object, distinct from the id of + * any shape in the list. We need a fresh shape for all deletions, even + * of lastProp. Otherwise, a shape number could replay and caches might + * return get deleted DictionaryShapes! See bug 595365. */ - setOwnShape(lastProp->shape); + flags |= OWN_SHAPE; Shape *oldLastProp = lastProp; shape->removeFromDictionary(this); @@ -1132,7 +1166,7 @@ JSObject::removeProperty(JSContext *cx, jsid id) } updateShape(cx); - /* Last, consider shrinking table if its load factor is <= .25. */ + /* On the way out, consider shrinking table if its load factor is <= .25. */ if (PropertyTable *table = lastProp->table) { uint32 size = table->capacity(); if (size > PropertyTable::MIN_SIZE && table->entryCount <= size >> 2) { diff --git a/js/src/jsscope.h b/js/src/jsscope.h index b85f5eef9b68..8604ef073476 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -808,6 +808,7 @@ struct JSScopeStats { jsrefcount redundantPuts; jsrefcount putFails; jsrefcount changes; + jsrefcount changePuts; jsrefcount changeFails; jsrefcount compresses; jsrefcount grows; diff --git a/js/src/tests/e4x/XMLList/regress-373072.js b/js/src/tests/e4x/XMLList/regress-373072.js index 38cf27fc6cd3..49d865c2b01f 100644 --- a/js/src/tests/e4x/XMLList/regress-373072.js +++ b/js/src/tests/e4x/XMLList/regress-373072.js @@ -47,8 +47,7 @@ START(summary); try { - expect = 'TypeError: cannot call namespace method on an XML list with ' + - '0 elements'; + expect = "TypeError: can't call namespace method on an XML list with 0 elements"; XML.prototype.function::namespace.call(new XMLList()); } catch(ex) diff --git a/js/src/tests/js1_6/Array/regress-304828.js b/js/src/tests/js1_6/Array/regress-304828.js index b77e47fa8e4e..432b59eaf9e1 100644 --- a/js/src/tests/js1_6/Array/regress-304828.js +++ b/js/src/tests/js1_6/Array/regress-304828.js @@ -101,7 +101,7 @@ reportCompare('abc', value, summary + ': push'); // pop value = 'abc'; -expect = "TypeError: property 'Array.prototype.pop.call(value)' is non-configurable and cannot be deleted"; +expect = "TypeError: property Array.prototype.pop.call(value) is non-configurable and can't be deleted"; try { actual = Array.prototype.pop.call(value); diff --git a/js/src/tests/js1_8_5/regress/jstests.list b/js/src/tests/js1_8_5/regress/jstests.list index acde672712c8..30eef57e7bfc 100644 --- a/js/src/tests/js1_8_5/regress/jstests.list +++ b/js/src/tests/js1_8_5/regress/jstests.list @@ -23,6 +23,7 @@ script regress-566549.js script regress-566914.js script regress-567152.js script regress-569306.js +script regress-569464.js script regress-571014.js script regress-577648-1.js script regress-577648-2.js @@ -37,8 +38,9 @@ fails-if(!xulRuntime.shell) script regress-595230-1.js fails-if(!xulRuntime.shell) script regress-595230-2.js script regress-595365-1.js fails-if(!xulRuntime.shell) script regress-595365-2.js -script regress-569464.js script regress-596103.js +script regress-596805-1.js +script regress-596805-2.js script regress-597870.js fails-if(!xulRuntime.shell) script regress-597945-1.js script regress-597945-2.js diff --git a/js/src/tests/js1_8_5/regress/regress-596805-1.js b/js/src/tests/js1_8_5/regress/regress-596805-1.js new file mode 100644 index 000000000000..391c6fa1f823 --- /dev/null +++ b/js/src/tests/js1_8_5/regress/regress-596805-1.js @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var f = function(){}; +for (var y in f); +f.j = 0; +Object.defineProperty(f, "k", ({configurable: true})); +delete f.j; +Object.defineProperty(f, "k", ({get: function() {}})); + +reportCompare(0, 0, "ok"); diff --git a/js/src/tests/js1_8_5/regress/regress-596805-2.js b/js/src/tests/js1_8_5/regress/regress-596805-2.js new file mode 100644 index 000000000000..60625729430c --- /dev/null +++ b/js/src/tests/js1_8_5/regress/regress-596805-2.js @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +for each(let c in [0, 0, 0, 0, 0]) { + try { (function() { + this.c = this; + this.e = arguments; + Object.defineProperty(this, "d", { + get: Math.pow, + configurable: true + }); + delete this.e; + delete this.c; + Object.defineProperty(this, "d", { + writable: true + }); + if (Math.tan( - 1)) { + Object.defineProperty(this, {}); + } + } (c)); + } catch(e) {} +} + +reportCompare(0, 0, "ok");