Bug 1125624, part 2 - Change js::StandardDefineProperty to forward to js::DefineProperty. r=Waldo.

This commit is contained in:
Jason Orendorff
2015-05-29 17:31:43 -05:00
parent 2768d1ce46
commit edf88f1a09

View File

@@ -276,403 +276,11 @@ js::Throw(JSContext* cx, JSObject* obj, unsigned errorNumber)
/*** Standard-compliant property definition (used by Object.defineProperty) **********************/
static bool
DefinePropertyOnObject(JSContext* cx, HandleNativeObject obj, HandleId id,
Handle<PropertyDescriptor> desc, ObjectOpResult& result)
{
/* 8.12.9 step 1. */
RootedShape shape(cx);
MOZ_ASSERT(!obj->getOps()->lookupProperty);
if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
return false;
MOZ_ASSERT(!obj->getOps()->defineProperty);
/* 8.12.9 steps 2-4. */
if (!shape) {
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return result.fail(JSMSG_OBJECT_NOT_EXTENSIBLE);
if (desc.isGenericDescriptor() || desc.isDataDescriptor()) {
MOZ_ASSERT(!obj->getOps()->defineProperty);
RootedValue v(cx, desc.hasValue() ? desc.value() : UndefinedValue());
unsigned attrs = desc.attributes() & (JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
if (!desc.hasConfigurable())
attrs |= JSPROP_PERMANENT;
if (!desc.hasWritable())
attrs |= JSPROP_READONLY;
return NativeDefineProperty(cx, obj, id, v, nullptr, nullptr, attrs, result);
}
MOZ_ASSERT(desc.isAccessorDescriptor());
unsigned attrs = desc.attributes() & (JSPROP_PERMANENT | JSPROP_ENUMERATE |
JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER);
if (!desc.hasConfigurable())
attrs |= JSPROP_PERMANENT;
return NativeDefineProperty(cx, obj, id, UndefinedHandleValue,
desc.getter(), desc.setter(), attrs, result);
}
/* 8.12.9 steps 5-6 (note 5 is merely a special case of 6). */
RootedValue v(cx);
bool shapeDataDescriptor = true,
shapeAccessorDescriptor = false,
shapeWritable = true,
shapeConfigurable = true,
shapeEnumerable = true,
shapeHasDefaultGetter = true,
shapeHasDefaultSetter = true,
shapeHasGetterValue = false,
shapeHasSetterValue = false;
uint8_t shapeAttributes = GetShapeAttributes(obj, shape);
if (!IsImplicitDenseOrTypedArrayElement(shape)) {
shapeDataDescriptor = shape->isDataDescriptor();
shapeAccessorDescriptor = shape->isAccessorDescriptor();
shapeWritable = shape->writable();
shapeConfigurable = shape->configurable();
shapeEnumerable = shape->enumerable();
shapeHasDefaultGetter = shape->hasDefaultGetter();
shapeHasDefaultSetter = shape->hasDefaultSetter();
shapeHasGetterValue = shape->hasGetterValue();
shapeHasSetterValue = shape->hasSetterValue();
shapeAttributes = shape->attributes();
}
do {
if (desc.isAccessorDescriptor()) {
if (!shapeAccessorDescriptor)
break;
if (desc.hasGetterObject()) {
if (!shape->hasGetterValue() || desc.getterObject() != shape->getterObject())
break;
}
if (desc.hasSetterObject()) {
if (!shape->hasSetterValue() || desc.setterObject() != shape->setterObject())
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 (IsImplicitDenseOrTypedArrayElement(shape)) {
v = obj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
} else if (shape->isDataDescriptor()) {
/*
* We must rule out a non-configurable js::SetterOp-guarded
* property becoming a writable unguarded data property, since
* such a property can have its value changed to one the getter
* and setter preclude.
*
* A desc lacking writable but with value is a data descriptor
* and we must reject it as if it had writable: true if current
* is writable.
*/
if (!shape->configurable() &&
(!shape->hasDefaultGetter() || !shape->hasDefaultSetter()) &&
desc.isDataDescriptor() &&
(desc.hasWritable() ? desc.writable() : shape->writable()))
{
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
if (!NativeGetExistingProperty(cx, obj, obj, shape, &v))
return false;
}
if (desc.isDataDescriptor()) {
if (!shapeDataDescriptor)
break;
bool same;
if (desc.hasValue()) {
if (!SameValue(cx, desc.value(), v, &same))
return false;
if (!same) {
/*
* Insist that a non-configurable js::GetterOp data
* property is frozen at exactly the last-got value.
*
* Duplicate the first part of the big conjunction that
* we tested above, rather than add a local bool flag.
* Likewise, don't try to keep shape->writable() in a
* flag we veto from true to false for non-configurable
* GetterOp-based data properties and test before the
* SameValue check later on in order to re-use that "if
* (!SameValue) return false" logic.
*
* This function is large and complex enough that it
* seems best to repeat a small bit of code and return
* result.fail() ASAP, instead of being clever.
*/
if (!shapeConfigurable &&
(!shape->hasDefaultGetter() || !shape->hasDefaultSetter()))
{
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
break;
}
}
if (desc.hasWritable() && desc.writable() != shapeWritable)
break;
} else {
/* The only fields in desc will be handled below. */
MOZ_ASSERT(desc.isGenericDescriptor());
}
}
if (desc.hasConfigurable() && desc.configurable() != shapeConfigurable)
break;
if (desc.hasEnumerable() && desc.enumerable() != shapeEnumerable)
break;
/* The conditions imposed by step 5 or step 6 apply. */
return result.succeed();
} while (0);
/* 8.12.9 step 7. */
if (!shapeConfigurable) {
if ((desc.hasConfigurable() && desc.configurable()) ||
(desc.hasEnumerable() && desc.enumerable() != shape->enumerable())) {
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
}
bool callDelProperty = false;
if (desc.isGenericDescriptor()) {
/* 8.12.9 step 8, no validation required */
} else if (desc.isDataDescriptor() != shapeDataDescriptor) {
/* 8.12.9 step 9. */
if (!shapeConfigurable)
return result.fail(JSMSG_CANT_REDEFINE_PROP);
} else if (desc.isDataDescriptor()) {
/* 8.12.9 step 10. */
MOZ_ASSERT(shapeDataDescriptor);
if (!shapeConfigurable && !shape->writable()) {
if (desc.hasWritable() && desc.writable())
return result.fail(JSMSG_CANT_REDEFINE_PROP);
if (desc.hasValue()) {
bool same;
if (!SameValue(cx, desc.value(), v, &same))
return false;
if (!same)
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
}
callDelProperty = !shapeHasDefaultGetter || !shapeHasDefaultSetter;
} else {
/* 8.12.9 step 11. */
MOZ_ASSERT(desc.isAccessorDescriptor() && shape->isAccessorDescriptor());
if (!shape->configurable()) {
// The hasSetterValue() and hasGetterValue() calls below ought to
// be redundant here, because accessor shapes should always have
// both JSPROP_GETTER and JSPROP_SETTER. But this is not the case
// currently; in particular Object.defineProperty(obj, key, {get: fn})
// creates a property without JSPROP_SETTER (bug 1133315).
if (desc.hasSetterObject() &&
desc.setterObject() != (shape->hasSetterValue() ? shape->setterObject() : nullptr))
{
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
if (desc.hasGetterObject() &&
desc.getterObject() != (shape->hasGetterValue() ? shape->getterObject() : nullptr))
{
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
}
}
/* 8.12.9 step 12. */
unsigned attrs;
GetterOp getter;
SetterOp setter;
if (desc.isGenericDescriptor()) {
unsigned changed = 0;
if (desc.hasConfigurable())
changed |= JSPROP_PERMANENT;
if (desc.hasEnumerable())
changed |= JSPROP_ENUMERATE;
attrs = (shapeAttributes & ~changed) | (desc.attributes() & changed);
getter = IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->getter();
setter = IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->setter();
} else if (desc.isDataDescriptor()) {
/* Watch out for accessor -> data transformations here. */
unsigned changed = JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED;
unsigned descAttrs = desc.attributes();
if (desc.hasConfigurable())
changed |= JSPROP_PERMANENT;
if (desc.hasEnumerable())
changed |= JSPROP_ENUMERATE;
if (desc.hasWritable()) {
changed |= JSPROP_READONLY;
} else if (!shapeDataDescriptor) {
changed |= JSPROP_READONLY;
descAttrs |= JSPROP_READONLY;
}
if (desc.hasValue())
v = desc.value();
attrs = (descAttrs & changed) | (shapeAttributes & ~changed);
getter = nullptr;
setter = nullptr;
} else {
MOZ_ASSERT(desc.isAccessorDescriptor());
/* 8.12.9 step 12. */
unsigned changed = 0;
if (desc.hasConfigurable())
changed |= JSPROP_PERMANENT;
if (desc.hasEnumerable())
changed |= JSPROP_ENUMERATE;
if (desc.hasGetterObject())
changed |= JSPROP_GETTER | JSPROP_SHARED | JSPROP_READONLY | JSPROP_SHADOWABLE;
if (desc.hasSetterObject())
changed |= JSPROP_SETTER | JSPROP_SHARED | JSPROP_READONLY | JSPROP_SHADOWABLE;
attrs = (desc.attributes() & changed) | (shapeAttributes & ~changed);
if (desc.hasGetterObject())
getter = desc.getter();
else
getter = shapeHasGetterValue ? shape->getter() : nullptr;
if (desc.hasSetterObject())
setter = desc.setter();
else
setter = shapeHasSetterValue ? shape->setter() : nullptr;
}
/*
* Since "data" properties implemented using native C functions may rely on
* side effects during setting, we must make them aware that they have been
* "assigned"; deleting the property before redefining it does the trick.
* See bug 539766, where we ran into problems when we redefined
* arguments.length without making the property aware that its value had
* been changed (which would have happened if we had deleted it before
* redefining it or we had invoked its setter to change its value).
*/
if (callDelProperty) {
ObjectOpResult ignored;
if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, ignored))
return false;
}
return NativeDefineProperty(cx, obj, id, v, getter, setter, attrs, result);
}
/* ES6 20130308 draft 8.4.2.1 [[DefineOwnProperty]] */
static bool
DefinePropertyOnArray(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
Handle<PropertyDescriptor> desc, ObjectOpResult& result)
{
/* Step 2. */
if (id == NameToId(cx->names().length)) {
// Canonicalize value, if necessary, before proceeding any further. It
// would be better if this were always/only done by ArraySetLength.
// But canonicalization may throw a RangeError (or other exception, if
// the value is an object with user-defined conversion semantics)
// before other attributes are checked. So as long as our internal
// defineProperty hook doesn't match the ECMA one, this duplicate
// checking can't be helped.
RootedValue v(cx);
if (desc.hasValue()) {
uint32_t newLen;
if (!CanonicalizeArrayLengthValue(cx, desc.value(), &newLen))
return false;
v.setNumber(newLen);
} else {
v.setNumber(arr->length());
}
if (desc.hasConfigurable() && desc.configurable())
return result.fail(JSMSG_CANT_REDEFINE_PROP);
if (desc.hasEnumerable() && desc.enumerable())
return result.fail(JSMSG_CANT_REDEFINE_PROP);
if (desc.isAccessorDescriptor())
return result.fail(JSMSG_CANT_REDEFINE_PROP);
unsigned attrs = arr->lookup(cx, id)->attributes();
if (!arr->lengthIsWritable()) {
if (desc.hasWritable() && desc.writable())
return result.fail(JSMSG_CANT_REDEFINE_PROP);
} else {
if (desc.hasWritable() && !desc.writable())
attrs = attrs | JSPROP_READONLY;
}
return ArraySetLength(cx, arr, id, attrs, v, result);
}
/* Step 3. */
uint32_t index;
if (IdIsIndex(id, &index)) {
/* Step 3b. */
uint32_t oldLen = arr->length();
/* Steps 3a, 3e. */
if (index >= oldLen && !arr->lengthIsWritable())
return result.fail(JSMSG_CANT_APPEND_TO_ARRAY);
/* Steps 3f-j. */
return DefinePropertyOnObject(cx, arr, id, desc, result);
}
/* Step 4. */
return DefinePropertyOnObject(cx, arr, id, desc, result);
}
// ES6 draft rev31 9.4.5.3 [[DefineOwnProperty]]
static bool
DefinePropertyOnTypedArray(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc, ObjectOpResult& result)
{
MOZ_ASSERT(IsAnyTypedArray(obj));
// Steps 3.a-c.
uint64_t index;
if (IsTypedArrayIndex(id, &index))
return DefineTypedArrayElement(cx, obj, index, desc, result);
// Step 4.
return DefinePropertyOnObject(cx, obj.as<NativeObject>(), id, desc, result);
}
bool
js::StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc, ObjectOpResult& result)
{
if (obj->is<ArrayObject>()) {
Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
return DefinePropertyOnArray(cx, arr, id, desc, result);
}
if (IsAnyTypedArray(obj))
return DefinePropertyOnTypedArray(cx, obj, id, desc, result);
if (obj->is<ProxyObject>()) {
Rooted<PropertyDescriptor> pd(cx, desc);
pd.object().set(obj);
return Proxy::defineProperty(cx, obj, id, pd, result);
}
if (obj->getOps()->defineProperty)
return obj->getOps()->defineProperty(cx, obj, id, desc, result);
return DefinePropertyOnObject(cx, obj.as<NativeObject>(), id, desc, result);
return DefineProperty(cx, obj, id, desc, result);
}
bool
@@ -680,7 +288,7 @@ js::StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc)
{
ObjectOpResult success;
return StandardDefineProperty(cx, obj, id, desc, success) &&
return DefineProperty(cx, obj, id, desc, success) &&
success.checkStrict(cx, obj, id);
}