+ // 1. Let current be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
+ SparseArrayValueMap::AddResult result = map->add(this, index);
+ SparseArrayEntry* entryInMap = &result.iterator->value;
+
+ // 2. Let extensible be the value of the [[Extensible]] internal property of O.
+ // 3. If current is undefined and extensible is false, then Reject.
+ // 4. If current is undefined and extensible is true, then
+ if (result.isNewEntry) {
+ if (!isExtensible()) {
+ map->remove(result.iterator);
+ return reject(exec, throwException, "Attempting to define property on object that is not extensible.");
+ }
+
+ // 4.a. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then create an own data property
+ // named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values
+ // are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly
+ // created property is set to its default value.
+ // 4.b. Else, Desc must be an accessor Property Descriptor so, create an own accessor property named P of
+ // object O whose [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] attribute values are described by
+ // Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property
+ // is set to its default value.
+ // 4.c. Return true.
+
+ PropertyDescriptor defaults;
+ entryInMap->setWithoutWriteBarrier(jsUndefined());
+ entryInMap->attributes = DontDelete | DontEnum | ReadOnly;
+ entryInMap->get(defaults);
+
+ putIndexedDescriptor(exec, entryInMap, descriptor, defaults);
+ if (index >= m_butterfly->arrayStorage()->length())
+ m_butterfly->arrayStorage()->setLength(index + 1);
+ return true;
+ }
+
+ // 5. Return true, if every field in Desc is absent.
+ // 6. Return true, if every field in Desc also occurs in current and the value of every field in Desc is the same value as the corresponding field in current when compared using the SameValue algorithm (9.12).
+ PropertyDescriptor current;
+ entryInMap->get(current);
+ if (descriptor.isEmpty() || descriptor.equalTo(exec, current))
+ return true;
+
+ // 7. If the [[Configurable]] field of current is false then
+ if (!current.configurable()) {
+ // 7.a. Reject, if the [[Configurable]] field of Desc is true.
+ if (descriptor.configurablePresent() && descriptor.configurable())
+ return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property.");
+ // 7.b. Reject, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other.
+ if (descriptor.enumerablePresent() && current.enumerable() != descriptor.enumerable())
+ return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property.");
+ }
+
+ // 8. If IsGenericDescriptor(Desc) is true, then no further validation is required.
+ if (!descriptor.isGenericDescriptor()) {
+ // 9. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) have different results, then
+ if (current.isDataDescriptor() != descriptor.isDataDescriptor()) {
+ // 9.a. Reject, if the [[Configurable]] field of current is false.
+ if (!current.configurable())
+ return reject(exec, throwException, "Attempting to change access mechanism for an unconfigurable property.");
+ // 9.b. If IsDataDescriptor(current) is true, then convert the property named P of object O from a
+ // data property to an accessor property. Preserve the existing values of the converted property's
+ // [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to
+ // their default values.
+ // 9.c. Else, convert the property named P of object O from an accessor property to a data property.
+ // Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]]
+ // attributes and set the rest of the property's attributes to their default values.
+ } else if (current.isDataDescriptor() && descriptor.isDataDescriptor()) {
+ // 10. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
+ // 10.a. If the [[Configurable]] field of current is false, then
+ if (!current.configurable() && !current.writable()) {
+ // 10.a.i. Reject, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true.
+ if (descriptor.writable())
+ return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property.");
+ // 10.a.ii. If the [[Writable]] field of current is false, then
+ // 10.a.ii.1. Reject, if the [[Value]] field of Desc is present and SameValue(Desc.[[Value]], current.[[Value]]) is false.
+ if (descriptor.value() && !sameValue(exec, descriptor.value(), current.value()))
+ return reject(exec, throwException, "Attempting to change value of a readonly property.");
+ }
+ // 10.b. else, the [[Configurable]] field of current is true, so any change is acceptable.
+ } else {
+ ASSERT(current.isAccessorDescriptor() && current.getterPresent() && current.setterPresent());
+ // 11. Else, IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are both true so, if the [[Configurable]] field of current is false, then
+ if (!current.configurable()) {
+ // 11.i. Reject, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false.
+ if (descriptor.setterPresent() && descriptor.setter() != current.setter())
+ return reject(exec, throwException, "Attempting to change the setter of an unconfigurable property.");
+ // 11.ii. Reject, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false.
+ if (descriptor.getterPresent() && descriptor.getter() != current.getter())
+ return reject(exec, throwException, "Attempting to change the getter of an unconfigurable property.");
+ }
+ }
+ }
+
+ // 12. For each attribute field of Desc that is present, set the correspondingly named attribute of the property named P of object O to the value of the field.
+ putIndexedDescriptor(exec, entryInMap, descriptor, current);
+ // 13. Return true.
+ return true;
+}
+
+SparseArrayValueMap* JSObject::allocateSparseIndexMap(VM& vm)
+{
+ SparseArrayValueMap* result = SparseArrayValueMap::create(vm);
+ arrayStorage()->m_sparseMap.set(vm, this, result);
+ return result;
+}
+
+void JSObject::deallocateSparseIndexMap()
+{
+ if (ArrayStorage* arrayStorage = arrayStorageOrNull())
+ arrayStorage->m_sparseMap.clear();
+}
+
+bool JSObject::attemptToInterceptPutByIndexOnHoleForPrototype(ExecState* exec, JSValue thisValue, unsigned i, JSValue value, bool shouldThrow)
+{
+ for (JSObject* current = this; ;) {
+ // This has the same behavior with respect to prototypes as JSObject::put(). It only
+ // allows a prototype to intercept a put if (a) the prototype declares the property
+ // we're after rather than intercepting it via an override of JSObject::put(), and
+ // (b) that property is declared as ReadOnly or Accessor.
+
+ ArrayStorage* storage = current->arrayStorageOrNull();
+ if (storage && storage->m_sparseMap) {
+ SparseArrayValueMap::iterator iter = storage->m_sparseMap->find(i);
+ if (iter != storage->m_sparseMap->notFound() && (iter->value.attributes & (Accessor | ReadOnly))) {
+ iter->value.put(exec, thisValue, storage->m_sparseMap.get(), value, shouldThrow);
+ return true;
+ }
+ }
+
+ JSValue prototypeValue = current->prototype();
+ if (prototypeValue.isNull())
+ return false;
+
+ current = asObject(prototypeValue);
+ }
+}
+
+bool JSObject::attemptToInterceptPutByIndexOnHole(ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
+{
+ JSValue prototypeValue = prototype();
+ if (prototypeValue.isNull())
+ return false;
+
+ return asObject(prototypeValue)->attemptToInterceptPutByIndexOnHoleForPrototype(exec, this, i, value, shouldThrow);
+}
+
+template<IndexingType indexingShape>
+void JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState* exec, unsigned i, JSValue value)
+{
+ ASSERT((structure()->indexingType() & IndexingShapeMask) == indexingShape);
+ ASSERT(!indexingShouldBeSparse());
+
+ // For us to get here, the index is either greater than the public length, or greater than
+ // or equal to the vector length.
+ ASSERT(i >= m_butterfly->vectorLength());
+
+ VM& vm = exec->vm();
+
+ if (i >= MAX_ARRAY_INDEX - 1
+ || (i >= MIN_SPARSE_ARRAY_INDEX
+ && !isDenseEnoughForVector(i, countElements<indexingShape>(m_butterfly)))
+ || indexIsSufficientlyBeyondLengthForSparseMap(i, m_butterfly->vectorLength())) {
+ ASSERT(i <= MAX_ARRAY_INDEX);
+ ensureArrayStorageSlow(vm);
+ SparseArrayValueMap* map = allocateSparseIndexMap(vm);
+ map->putEntry(exec, this, i, value, false);
+ ASSERT(i >= arrayStorage()->length());
+ arrayStorage()->setLength(i + 1);
+ return;
+ }
+
+ ensureLength(vm, i + 1);
+
+ RELEASE_ASSERT(i < m_butterfly->vectorLength());
+ switch (indexingShape) {
+ case Int32Shape:
+ ASSERT(value.isInt32());
+ m_butterfly->contiguousInt32()[i].setWithoutWriteBarrier(value);
+ break;
+
+ case DoubleShape: {
+ ASSERT(value.isNumber());
+ double valueAsDouble = value.asNumber();
+ ASSERT(valueAsDouble == valueAsDouble);
+ m_butterfly->contiguousDouble()[i] = valueAsDouble;
+ break;
+ }
+
+ case ContiguousShape:
+ m_butterfly->contiguous()[i].set(vm, this, value);
+ break;
+
+ default:
+ CRASH();
+ }
+}
+
+void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, bool shouldThrow, ArrayStorage* storage)
+{
+ VM& vm = exec->vm();
+
+ // i should be a valid array index that is outside of the current vector.
+ ASSERT(i <= MAX_ARRAY_INDEX);
+ ASSERT(i >= storage->vectorLength());
+
+ SparseArrayValueMap* map = storage->m_sparseMap.get();
+
+ // First, handle cases where we don't currently have a sparse map.
+ if (LIKELY(!map)) {
+ // If the array is not extensible, we should have entered dictionary mode, and created the sparse map.
+ ASSERT(isExtensible());
+
+ // Update m_length if necessary.
+ if (i >= storage->length())
+ storage->setLength(i + 1);
+
+ // Check that it is sensible to still be using a vector, and then try to grow the vector.
+ if (LIKELY(!indexIsSufficientlyBeyondLengthForSparseMap(i, storage->vectorLength())
+ && isDenseEnoughForVector(i, storage->m_numValuesInVector)
+ && increaseVectorLength(vm, i + 1))) {
+ // success! - reread m_storage since it has likely been reallocated, and store to the vector.
+ storage = arrayStorage();
+ storage->m_vector[i].set(vm, this, value);
+ ++storage->m_numValuesInVector;
+ return;
+ }
+ // We don't want to, or can't use a vector to hold this property - allocate a sparse map & add the value.
+ map = allocateSparseIndexMap(exec->vm());
+ map->putEntry(exec, this, i, value, shouldThrow);
+ return;
+ }
+
+ // Update m_length if necessary.
+ unsigned length = storage->length();
+ if (i >= length) {
+ // Prohibit growing the array if length is not writable.
+ if (map->lengthIsReadOnly() || !isExtensible()) {
+ if (shouldThrow)
+ throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
+ return;
+ }
+ length = i + 1;
+ storage->setLength(length);
+ }
+
+ // We are currently using a map - check whether we still want to be doing so.
+ // We will continue to use a sparse map if SparseMode is set, a vector would be too sparse, or if allocation fails.
+ unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
+ if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->vm(), length)) {
+ map->putEntry(exec, this, i, value, shouldThrow);
+ return;
+ }
+
+ // Reread m_storage after increaseVectorLength, update m_numValuesInVector.
+ storage = arrayStorage();
+ storage->m_numValuesInVector = numValuesInArray;
+
+ // Copy all values from the map into the vector, and delete the map.
+ WriteBarrier<Unknown>* vector = storage->m_vector;
+ SparseArrayValueMap::const_iterator end = map->end();
+ for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it)
+ vector[it->key].set(vm, this, it->value.getNonSparseMode());
+ deallocateSparseIndexMap();
+
+ // Store the new property into the vector.
+ WriteBarrier<Unknown>& valueSlot = vector[i];
+ if (!valueSlot)
+ ++storage->m_numValuesInVector;
+ valueSlot.set(vm, this, value);
+}
+
+void JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
+{
+ VM& vm = exec->vm();
+
+ // i should be a valid array index that is outside of the current vector.
+ ASSERT(i <= MAX_ARRAY_INDEX);
+
+ switch (structure()->indexingType()) {
+ case ALL_BLANK_INDEXING_TYPES: {
+ if (indexingShouldBeSparse()) {
+ putByIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, shouldThrow,
+ ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
+ break;
+ }
+ if (indexIsSufficientlyBeyondLengthForSparseMap(i, 0) || i >= MIN_SPARSE_ARRAY_INDEX) {
+ putByIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, shouldThrow, createArrayStorage(vm, 0, 0));
+ break;
+ }
+ if (structure()->needsSlowPutIndexing()) {
+ ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, i + 1));
+ storage->m_vector[i].set(vm, this, value);
+ storage->m_numValuesInVector++;
+ break;
+ }
+
+ createInitialContiguous(vm, i + 1)[i].set(vm, this, value);
+ break;
+ }
+
+ case ALL_UNDECIDED_INDEXING_TYPES: {
+ CRASH();
+ break;
+ }
+
+ case ALL_INT32_INDEXING_TYPES: {
+ putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, i, value);
+ break;
+ }
+
+ case ALL_DOUBLE_INDEXING_TYPES: {
+ putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, i, value);
+ break;
+ }
+
+ case ALL_CONTIGUOUS_INDEXING_TYPES: {
+ putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, i, value);
+ break;
+ }
+
+ case NonArrayWithSlowPutArrayStorage:
+ case ArrayWithSlowPutArrayStorage: {
+ // No own property present in the vector, but there might be in the sparse map!
+ SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get();
+ if (!(map && map->contains(i)) && attemptToInterceptPutByIndexOnHole(exec, i, value, shouldThrow))
+ return;
+ // Otherwise, fall though.
+ }
+
+ case NonArrayWithArrayStorage:
+ case ArrayWithArrayStorage:
+ putByIndexBeyondVectorLengthWithArrayStorage(exec, i, value, shouldThrow, arrayStorage());
+ break;
+
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+}
+
+bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode, ArrayStorage* storage)
+{
+ VM& vm = exec->vm();
+
+ // i should be a valid array index that is outside of the current vector.
+ ASSERT(hasArrayStorage(structure()->indexingType()));
+ ASSERT(arrayStorage() == storage);
+ ASSERT(i >= storage->vectorLength() || attributes);
+ ASSERT(i <= MAX_ARRAY_INDEX);
+
+ SparseArrayValueMap* map = storage->m_sparseMap.get();
+
+ // First, handle cases where we don't currently have a sparse map.
+ if (LIKELY(!map)) {
+ // If the array is not extensible, we should have entered dictionary mode, and created the spare map.
+ ASSERT(isExtensible());
+
+ // Update m_length if necessary.
+ if (i >= storage->length())
+ storage->setLength(i + 1);
+
+ // Check that it is sensible to still be using a vector, and then try to grow the vector.
+ if (LIKELY(
+ !attributes
+ && (isDenseEnoughForVector(i, storage->m_numValuesInVector))
+ && !indexIsSufficientlyBeyondLengthForSparseMap(i, storage->vectorLength()))
+ && increaseVectorLength(vm, i + 1)) {
+ // success! - reread m_storage since it has likely been reallocated, and store to the vector.
+ storage = arrayStorage();
+ storage->m_vector[i].set(vm, this, value);
+ ++storage->m_numValuesInVector;
+ return true;
+ }
+ // We don't want to, or can't use a vector to hold this property - allocate a sparse map & add the value.
+ map = allocateSparseIndexMap(exec->vm());
+ return map->putDirect(exec, this, i, value, attributes, mode);
+ }
+
+ // Update m_length if necessary.
+ unsigned length = storage->length();
+ if (i >= length) {
+ if (mode != PutDirectIndexLikePutDirect) {
+ // Prohibit growing the array if length is not writable.
+ if (map->lengthIsReadOnly())
+ return reject(exec, mode == PutDirectIndexShouldThrow, StrictModeReadonlyPropertyWriteError);
+ if (!isExtensible())
+ return reject(exec, mode == PutDirectIndexShouldThrow, "Attempting to define property on object that is not extensible.");
+ }
+ length = i + 1;
+ storage->setLength(length);
+ }
+
+ // We are currently using a map - check whether we still want to be doing so.
+ // We will continue to use a sparse map if SparseMode is set, a vector would be too sparse, or if allocation fails.
+ unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
+ if (map->sparseMode() || attributes || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->vm(), length))
+ return map->putDirect(exec, this, i, value, attributes, mode);
+
+ // Reread m_storage after increaseVectorLength, update m_numValuesInVector.
+ storage = arrayStorage();
+ storage->m_numValuesInVector = numValuesInArray;
+
+ // Copy all values from the map into the vector, and delete the map.
+ WriteBarrier<Unknown>* vector = storage->m_vector;
+ SparseArrayValueMap::const_iterator end = map->end();
+ for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it)
+ vector[it->key].set(vm, this, it->value.getNonSparseMode());
+ deallocateSparseIndexMap();
+
+ // Store the new property into the vector.
+ WriteBarrier<Unknown>& valueSlot = vector[i];
+ if (!valueSlot)
+ ++storage->m_numValuesInVector;
+ valueSlot.set(vm, this, value);
+ return true;
+}
+
+bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode)
+{
+ VM& vm = exec->vm();
+
+ // i should be a valid array index that is outside of the current vector.
+ ASSERT(i <= MAX_ARRAY_INDEX);
+
+ if (attributes & (ReadOnly | Accessor))
+ notifyPresenceOfIndexedAccessors(vm);
+
+ switch (structure()->indexingType()) {
+ case ALL_BLANK_INDEXING_TYPES: {
+ if (indexingShouldBeSparse() || attributes) {
+ return putDirectIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, attributes, mode,
+ ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
+ }
+ if (i >= MIN_SPARSE_ARRAY_INDEX) {
+ return putDirectIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, attributes, mode, createArrayStorage(vm, 0, 0));
+ }
+ if (structure()->needsSlowPutIndexing()) {
+ ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, i + 1));
+ storage->m_vector[i].set(vm, this, value);
+ storage->m_numValuesInVector++;
+ return true;
+ }
+
+ createInitialContiguous(vm, i + 1)[i].set(vm, this, value);
+ return true;
+ }
+
+ case ALL_UNDECIDED_INDEXING_TYPES: {
+ convertUndecidedForValue(exec->vm(), value);
+ // Reloop.
+ return putDirectIndex(exec, i, value, attributes, mode);
+ }
+
+ case ALL_INT32_INDEXING_TYPES: {
+ if (attributes & (ReadOnly | Accessor)) {
+ return putDirectIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, attributes, mode, convertInt32ToArrayStorage(vm));
+ }
+ if (!value.isInt32()) {
+ convertInt32ForValue(vm, value);
+ return putDirectIndexBeyondVectorLength(exec, i, value, attributes, mode);
+ }
+ putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, i, value);
+ return true;
+ }
+
+ case ALL_DOUBLE_INDEXING_TYPES: {
+ if (attributes & (ReadOnly | Accessor)) {
+ return putDirectIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, attributes, mode, convertDoubleToArrayStorage(vm));
+ }
+ if (!value.isNumber()) {
+ convertDoubleToContiguous(vm);
+ return putDirectIndexBeyondVectorLength(exec, i, value, attributes, mode);
+ }
+ double valueAsDouble = value.asNumber();
+ if (valueAsDouble != valueAsDouble) {
+ convertDoubleToContiguous(vm);
+ return putDirectIndexBeyondVectorLength(exec, i, value, attributes, mode);
+ }
+ putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, i, value);
+ return true;
+ }
+
+ case ALL_CONTIGUOUS_INDEXING_TYPES: {
+ if (attributes & (ReadOnly | Accessor)) {
+ return putDirectIndexBeyondVectorLengthWithArrayStorage(
+ exec, i, value, attributes, mode, convertContiguousToArrayStorage(vm));
+ }
+ putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, i, value);
+ return true;
+ }
+
+ case ALL_ARRAY_STORAGE_INDEXING_TYPES:
+ return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, arrayStorage());
+
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return false;
+ }
+}
+
+void JSObject::putDirectNativeFunction(ExecState* exec, JSGlobalObject* globalObject, const PropertyName& propertyName, unsigned functionLength, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes)
+{
+ StringImpl* name = propertyName.publicName();
+ ASSERT(name);
+
+ JSFunction* function =
+ JSFunction::create(exec, globalObject, functionLength, name, nativeFunction, intrinsic);
+ putDirect(exec->vm(), propertyName, function, attributes);
+}
+
+ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned currentVectorLength, unsigned currentLength, unsigned desiredLength)
+{
+ ASSERT(desiredLength <= MAX_STORAGE_VECTOR_LENGTH);
+
+ unsigned increasedLength;
+ unsigned maxInitLength = std::min(currentLength, 100000U);