Bug 1148652, part 1 - Move array-specific special cases to the top of NativeDefineProperty; update ArraySetLength to be able to cope with incomplete attrs. r=efaust.

This commit is contained in:
Jason Orendorff 2015-03-23 14:32:30 -05:00
parent 86ac038c99
commit 0859395dae
3 changed files with 84 additions and 61 deletions

View File

@ -2595,7 +2595,7 @@ class PropertyDescriptorOperations
MOZ_ASSERT(!has(JSPROP_IGNORE_READONLY));
MOZ_ASSERT(!has(JSPROP_IGNORE_VALUE));
MOZ_ASSERT(!has(SHADOWABLE));
MOZ_ASSERT(desc()->value.isUndefined());
MOZ_ASSERT(value().isUndefined());
MOZ_ASSERT_IF(!has(JSPROP_GETTER), !getter());
MOZ_ASSERT_IF(!has(JSPROP_SETTER), !setter());
} else {

View File

@ -505,7 +505,7 @@ js::CanonicalizeArrayLengthValue(JSContext* cx, HandleValue v, uint32_t* newLen)
return false;
}
/* ES6 20130308 draft 8.4.2.4 ArraySetLength */
/* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */
bool
js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
unsigned attrs, HandleValue value, ObjectOpResult& result)
@ -515,26 +515,26 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
if (!arr->maybeCopyElementsForWrite(cx))
return false;
/* Steps 1-2 are irrelevant in our implementation. */
/* Steps 3-5. */
// Step 1.
uint32_t newLen;
if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
return false;
if (attrs & JSPROP_IGNORE_VALUE) {
// The spec has us calling OrdinaryDefineOwnProperty if
// Desc.[[Value]] is absent, but our implementation is so different that
// this is impossible. Instead, set newLen to the current length and
// proceed to step 9.
newLen = arr->length();
} else {
// Step 2 is irrelevant in our implementation.
// Abort if we're being asked to change enumerability or configurability.
// (The length property of arrays is non-configurable, so such attempts
// must fail.) This behavior is spread throughout the ArraySetLength spec
// algorithm, but we only need check it once as our array implementation
// is internally so different from the spec algorithm. (ES5 and ES6 define
// behavior by delegating to the default define-own-property algorithm --
// OrdinaryDefineOwnProperty in ES6, the default [[DefineOwnProperty]] in
// ES5 -- but we reimplement all the conflict-detection bits ourselves here
// so that we can use a customized length representation.)
if (!(attrs & JSPROP_PERMANENT) || (attrs & JSPROP_ENUMERATE))
return result.fail(JSMSG_CANT_REDEFINE_PROP);
// Steps 3-7.
MOZ_ASSERT_IF(attrs & JSPROP_IGNORE_VALUE, value.isUndefined());
if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
return false;
/* Steps 6-7. */
// Step 8 is irrelevant in our implementation.
}
// Steps 9-11.
bool lengthIsWritable = arr->lengthIsWritable();
#ifdef DEBUG
{
@ -543,10 +543,21 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
MOZ_ASSERT(lengthShape->writable() == lengthIsWritable);
}
#endif
uint32_t oldLen = arr->length();
/* Steps 8-9 for arrays with non-writable length. */
// Part of steps 1.a, 12.a, and 16: Fail if we're being asked to change
// enumerability or configurability, or otherwise break the object
// invariants. (ES6 checks these by calling OrdinaryDefineOwnProperty, but
// in SM, the array length property is hardly ordinary.)
if ((attrs & (JSPROP_PERMANENT | JSPROP_IGNORE_PERMANENT)) == 0 ||
(attrs & (JSPROP_ENUMERATE | JSPROP_IGNORE_ENUMERATE)) == JSPROP_ENUMERATE ||
(attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0 ||
(!lengthIsWritable && (attrs & (JSPROP_READONLY | JSPROP_IGNORE_READONLY)) == 0))
{
return result.fail(JSMSG_CANT_REDEFINE_PROP);
}
// Steps 12-13 for arrays with non-writable length.
if (!lengthIsWritable) {
if (newLen == oldLen)
return result.succeed();
@ -554,7 +565,7 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
return result.fail(JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
}
/* Step 8. */
// Step 19.
bool succeeded = true;
do {
// The initialized length and capacity of an array only need updating
@ -616,10 +627,10 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
// If we're removing a relatively small number of elements, just do
// it exactly by the spec.
while (newLen < oldLen) {
/* Step 15a. */
// Step 15a.
oldLen--;
/* Steps 15b-d. */
// Steps 15b-d.
ObjectOpResult deleteSucceeded;
if (!DeleteElement(cx, arr, oldLen, deleteSucceeded))
return false;
@ -679,7 +690,7 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
MOZ_ASSERT(indexes[i] < index, "indexes should never repeat");
index = indexes[i];
/* Steps 15b-d. */
// Steps 15b-d.
ObjectOpResult deleteSucceeded;
if (!DeleteElement(cx, arr, index, deleteSucceeded))
return false;
@ -692,23 +703,25 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
}
} while (false);
/* Steps 12, 16. */
// Yes, we totally drop a non-stub getter/setter from a defineProperty
// API call on the floor here. Given that getter/setter will go away in
// the long run, with accessors replacing them both internally and at the
// API level, just run with this.
RootedShape lengthShape(cx, arr->lookup(cx, id));
if (!NativeObject::changeProperty(cx, arr, lengthShape,
attrs | JSPROP_PERMANENT | JSPROP_SHARED |
(lengthShape->attributes() & JSPROP_READONLY),
array_length_getter, array_length_setter))
{
return false;
}
// Update array length. Technically we should have been doing this
// throughout the loop, in step 19.d.iii.
arr->setLength(cx, newLen);
// Step 20.
if (attrs & JSPROP_READONLY) {
// Yes, we totally drop a non-stub getter/setter from a defineProperty
// API call on the floor here. Given that getter/setter will go away in
// the long run, with accessors replacing them both internally and at the
// API level, just run with this.
RootedShape lengthShape(cx, arr->lookup(cx, id));
if (!NativeObject::changeProperty(cx, arr, lengthShape,
lengthShape->attributes() | JSPROP_READONLY,
array_length_getter, array_length_setter))
{
return false;
}
}
// All operations past here until the |!succeeded| code must be infallible,
// so that all element fields remain properly synchronized.

View File

@ -1277,6 +1277,37 @@ js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
{
desc_.assertValid();
// Section numbers and step numbers below refer to ES6 draft rev 36
// (17 March 2015).
//
// This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as
// the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2
// (arguments), and 9.4.5.3 (typed array views).
// Dispense with custom behavior of exotic native objects first.
if (obj->is<ArrayObject>()) {
// 9.4.2.1 step 2. Redefining an array's length is very special.
Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
if (id == NameToId(cx->names().length)) {
if (!cx->shouldBeJSContext())
return false;
return ArraySetLength(cx->asJSContext(), arr, id, desc_.attributes(), desc_.value(),
result);
}
// 9.4.2.1 step 3. Don't extend a fixed-length array.
uint32_t index;
if (IdIsIndex(id, &index)) {
if (WouldDefinePastNonwritableLength(obj, index))
return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
}
} else if (IsAnyTypedArray(obj)) {
// Don't define new indexed properties on typed arrays.
uint64_t index;
if (IsTypedArrayIndex(id, &index))
return result.succeed();
}
Rooted<PropertyDescriptor> desc(cx, desc_);
RootedShape shape(cx);
@ -1413,27 +1444,6 @@ js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
// clear it.
desc.setAttributes(ApplyOrDefaultAttributes(desc.attributes()) & ~JSPROP_IGNORE_VALUE);
if (obj->is<ArrayObject>()) {
Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
if (id == NameToId(cx->names().length)) {
if (!cx->shouldBeJSContext())
return false;
return ArraySetLength(cx->asJSContext(), arr, id, desc.attributes(), desc.value(),
result);
}
uint32_t index;
if (IdIsIndex(id, &index)) {
if (WouldDefinePastNonwritableLength(obj, index))
return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
}
} else if (IsAnyTypedArray(obj)) {
// Don't define new indexed properties on typed arrays.
uint64_t index;
if (IsTypedArrayIndex(id, &index))
return result.succeed();
}
// At this point, no mutation has happened yet, but all ES6 error cases
// have been dealt with.
if (!AddOrChangeProperty(cx, obj, id, desc))