/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.types.builtins; import static com.github.anba.es6draft.runtime.AbstractOperations.*; import static com.github.anba.es6draft.runtime.internal.Errors.newRangeError; import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Spliterator; import java.util.stream.Stream; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.objects.binary.TypedArrayObject; import com.github.anba.es6draft.runtime.types.BuiltinSymbol; import com.github.anba.es6draft.runtime.types.Constructor; import com.github.anba.es6draft.runtime.types.Intrinsics; import com.github.anba.es6draft.runtime.types.Property; import com.github.anba.es6draft.runtime.types.PropertyDescriptor; import com.github.anba.es6draft.runtime.types.ScriptObject; import com.github.anba.es6draft.runtime.types.Type; /** * <h1>9 Ordinary and Exotic Objects Behaviours</h1><br> * <h2>9.4 Built-in Exotic Object Internal Methods and Data Fields</h2> * <ul> * <li>9.4.2 Array Exotic Objects * </ul> */ public class ArrayObject extends OrdinaryObject { private boolean hasIndexedAccessors = false; private boolean lengthWritable = true; private long length = 0; /** * Constructs a new Array object. * * @param realm * the realm object */ public ArrayObject(Realm realm) { super(realm); } /** * Returns the array's length. * * @return the array's length */ @Override public final long getLength() { return length; } /** * Sets the array's length. * * @param length * the new array length */ public final void setLengthUnchecked(long length) { assert this.length <= length && lengthWritable; this.length = length; } /** * Returns the own, dense element from the requested index. * * @param propertyKey * the indexed property key * @return the property value */ public final Object getDenseElement(long propertyKey) { assert isDenseArray() && propertyKey < length; return getIndexed(propertyKey); } /** * Returns {@code true} if the array has indexed accessors. * * @return {@code true} if the array has indexed accessors */ @Override public final boolean hasIndexedAccessors() { return hasIndexedAccessors; } @Override public String className() { return "Array"; } /** * 9.4.2 Array Exotic Objects * <p> * Introductory paragraph * * @param p * the property key * @return {@code true} if the property key is a valid array index */ private static boolean isArrayIndex(long p) { return 0 <= p && p < 0xFFFF_FFFFL; } private boolean isCompatibleLengthProperty(PropertyDescriptor desc, long newLength) { if (desc.isEmpty()) { return true; } if (desc.isAccessorDescriptor() || desc.isConfigurable() || desc.isEnumerable()) { return false; } if (desc.isGenericDescriptor()) { return true; } assert desc.isDataDescriptor(); if (!lengthWritable && (desc.isWritable() || (desc.hasValue() && newLength != length))) { return false; } return true; } private boolean defineLength(PropertyDescriptor desc, long newLength) { assert desc.hasValue() ? newLength >= 0 : newLength < 0; boolean succeeded = isCompatibleLengthProperty(desc, newLength); if (succeeded) { if (newLength >= 0) { length = newLength; } if (desc.hasWritable()) { lengthWritable = desc.isWritable(); } } return succeeded; } private boolean defineLength(long newLength) { assert newLength >= 0; boolean succeeded = (lengthWritable || newLength == length); if (succeeded) { length = newLength; } return succeeded; } @Override protected final boolean setPropertyValue(ExecutionContext cx, String propertyKey, Object value, Property current) { if ("length".equals(propertyKey)) { return ArraySetLength(cx, this, value); } return super.setPropertyValue(cx, propertyKey, value, current); } @Override protected final boolean has(ExecutionContext cx, String propertyKey) { if ("length".equals(propertyKey)) { return true; } return super.has(cx, propertyKey); } @Override protected final boolean hasOwnProperty(ExecutionContext cx, String propertyKey) { if ("length".equals(propertyKey)) { return true; } return super.hasOwnProperty(cx, propertyKey); } @Override protected final Property getProperty(ExecutionContext cx, String propertyKey) { if ("length".equals(propertyKey)) { return new Property(length, lengthWritable, false, false); } return super.getProperty(cx, propertyKey); } @Override protected final boolean deleteProperty(ExecutionContext cx, String propertyKey) { if ("length".equals(propertyKey)) { return false; } return super.deleteProperty(cx, propertyKey); } @Override protected final List<Object> getOwnPropertyKeys(ExecutionContext cx) { int totalSize = countProperties(true) + 1; // + 1 for length property /* step 1 */ ArrayList<Object> ownKeys = new ArrayList<>(totalSize); /* step 2 */ appendIndexedProperties(ownKeys); /* step 3 */ ownKeys.add("length"); appendProperties(ownKeys); /* step 4 */ appendSymbolProperties(ownKeys); /* step 5 */ return ownKeys; } /** * 9.4.2.1 [[DefineOwnProperty]] (P, Desc) */ @Override protected final boolean defineProperty(ExecutionContext cx, long propertyKey, PropertyDescriptor desc) { /* steps 1-2 (not applicable) */ /* step 3 */ if (isArrayIndex(propertyKey)) { /* steps 3.a-3.c */ long oldLen = this.length; /* steps 3.d-3.e */ long index = propertyKey; /* steps 3.f */ if (index >= oldLen && !lengthWritable) { return false; } /* steps 3.g-3.h */ boolean succeeded = ordinaryDefineOwnProperty(cx, propertyKey, desc); /* step 3.i */ if (!succeeded) { return false; } /* step 3.j */ if (index >= oldLen) { this.length = index + 1; } hasIndexedAccessors |= desc.isAccessorDescriptor(); /* step 3.k */ return true; } /* step 4 */ return ordinaryDefineOwnProperty(cx, propertyKey, desc); } /** * 9.4.2.1 [[DefineOwnProperty]] (P, Desc) */ @Override protected final boolean defineProperty(ExecutionContext cx, String propertyKey, PropertyDescriptor desc) { /* steps 1, 3 (not applicable) */ /* step 2 */ if ("length".equals(propertyKey)) { return ArraySetLength(cx, this, desc); } /* step 4 */ return ordinaryDefineOwnProperty(cx, propertyKey, desc); } /** * 9.4.2.2 ArrayCreate(length, proto) * * @param cx * the execution context * @param length * the array length * @return the new array object */ public static ArrayObject ArrayCreate(ExecutionContext cx, int length) { return ArrayCreate(cx, length, cx.getIntrinsic(Intrinsics.ArrayPrototype)); } /** * 9.4.2.2 ArrayCreate(length, proto) * * @param cx * the execution context * @param length * the array length * @return the new array object */ public static ArrayObject ArrayCreate(ExecutionContext cx, long length) { return ArrayCreate(cx, length, cx.getIntrinsic(Intrinsics.ArrayPrototype)); } /** * 9.4.2.2 ArrayCreate(length, proto) * * @param cx * the execution context * @param length * the array length * @param proto * the prototype object * @return the new array object */ public static ArrayObject ArrayCreate(ExecutionContext cx, int length, ScriptObject proto) { assert proto != null; /* steps 1-2 */ assert length >= 0; /* steps 3-4 (not applicable) */ /* steps 5-7, 9 (implicit) */ ArrayObject array = new ArrayObject(cx.getRealm()); /* step 8 */ array.setPrototype(proto); /* steps 10-11 */ array.length = length; /* step 12 */ return array; } /** * 9.4.2.2 ArrayCreate(length, proto) * * @param cx * the execution context * @param length * the array length * @param proto * the prototype object * @return the new array object */ public static ArrayObject ArrayCreate(ExecutionContext cx, long length, ScriptObject proto) { assert proto != null; /* steps 1-2 */ assert length >= 0; /* step 3 */ if (length > 0xFFFF_FFFFL) { // enforce array index invariant throw newRangeError(cx, Messages.Key.InvalidArrayLength); } /* step 4 (not applicable) */ /* steps 5-7, 9 (implicit) */ ArrayObject array = new ArrayObject(cx.getRealm()); /* step 8 */ array.setPrototype(proto); /* steps 10-11 */ array.length = length; /* step 12 */ return array; } /** * Helper method to create dense arrays. * * @param cx * the execution context * @param proto * the prototype object * @param values * the element values * @return the new array object */ public static ArrayObject DenseArrayCreate(ExecutionContext cx, ScriptObject proto, Object... values) { ArrayObject array = ArrayCreate(cx, values.length, proto); for (int i = 0, len = values.length; i < len; ++i) { array.setIndexed(i, values[i]); } return array; } /** * Helper method to create dense arrays. * * @param cx * the execution context * @param values * the element values * @return the new array object */ public static ArrayObject DenseArrayCreate(ExecutionContext cx, Collection<?> values) { ArrayObject array = ArrayCreate(cx, values.size()); int i = 0; for (Object value : values) { array.setIndexed(i++, value); } return array; } /** * Helper method to create dense arrays. * * @param cx * the execution context * @param values * the element values * @return the new array object */ public static ArrayObject DenseArrayCreate(ExecutionContext cx, List<?> values) { ArrayObject array = ArrayCreate(cx, values.size()); int i = 0; for (Object value : values) { array.setIndexed(i++, value); } return array; } /** * Helper method to create dense arrays. * * @param cx * the execution context * @param values * the element values * @return the new array object */ public static ArrayObject DenseArrayCreate(ExecutionContext cx, Stream<?> values) { Spliterator<?> spliterator = values.spliterator(); ArrayObject array = ArrayCreate(cx, Math.max(spliterator.getExactSizeIfKnown(), 0)); spliterator.forEachRemaining(value -> { array.setIndexed(array.getIndexedLength(), value); }); array.setLengthUnchecked(array.getIndexedLength()); return array; } /** * Helper method to create dense arrays. * <p> * [Called from generated code] * * @param cx * the execution context * @param values * the element values * @return the new array object */ public static ArrayObject DenseArrayCreate(ExecutionContext cx, Object... values) { ArrayObject array = ArrayCreate(cx, values.length); for (int i = 0, len = values.length; i < len; ++i) { array.setIndexed(i, values[i]); } return array; } /** * Helper method to create sparse arrays. * <p> * [Called from generated code] * * @param cx * the execution context * @param values * the element values * @return the new array object */ public static ArrayObject SparseArrayCreate(ExecutionContext cx, Object[] values) { ArrayObject array = ArrayCreate(cx, values.length); for (int i = 0, len = values.length; i < len; ++i) { if (values[i] != null) { array.setIndexed(i, values[i]); } } return array; } /** * 9.4.2.3 ArraySpeciesCreate(originalArray, length) * * @param cx * the execution context * @param orginalArray * the source array * @param length * the array length * @return the new array object */ public static ScriptObject ArraySpeciesCreate(ExecutionContext cx, ScriptObject orginalArray, long length) { /* step 1 */ assert length >= 0; /* step 2 (not applicable) */ /* step 3 */ Object c = UNDEFINED; /* steps 4-6 */ if (IsArray(cx, orginalArray)) { /* steps 6.a-b */ c = Get(cx, orginalArray, "constructor"); /* step 6.c */ if (IsConstructor(c)) { Constructor constructor = (Constructor) c; /* step 6.c.i */ Realm thisRealm = cx.getRealm(); /* steps 6.c.ii-iii */ Realm realmC = GetFunctionRealm(cx, constructor); /* step 6.c.iv */ if (thisRealm != realmC && constructor == realmC.getIntrinsic(Intrinsics.Array)) { c = UNDEFINED; } } /* step 6.d */ if (Type.isObject(c)) { c = Get(cx, Type.objectValue(c), BuiltinSymbol.species.get()); if (Type.isNull(c)) { c = UNDEFINED; } } } /* step 7 */ if (Type.isUndefined(c)) { return ArrayCreate(cx, length); } /* step 8 */ if (!IsConstructor(c)) { throw newTypeError(cx, Messages.Key.NotConstructor); } /* step 9 */ return ((Constructor) c).construct(cx, (Constructor) c, length); } /** * 9.4.2.4 ArraySetLength(A, Desc) * * @param cx * the execution context * @param array * the array object * @param desc * the property descriptor * @return {@code true} on success */ public static boolean ArraySetLength(ExecutionContext cx, ArrayObject array, PropertyDescriptor desc) { /* step 1 */ if (!desc.hasValue()) { return array.defineLength(desc, -1); } /* step 2 */ PropertyDescriptor newLenDesc = desc.clone(); /* steps 3-4 */ long newLen = ToUint32(cx, desc.getValue()); /* steps 5-6 */ double numberLen = ToNumber(cx, desc.getValue()); /* step 7 */ if (newLen != numberLen) { throw newRangeError(cx, Messages.Key.InvalidArrayLength); } /* step 8 */ newLenDesc.setValue(newLen); /* steps 9-11 */ long oldLen = array.length; /* step 12 */ if (newLen >= oldLen) { return array.defineLength(newLenDesc, newLen); } /* step 13 */ if (!array.lengthWritable) { return false; } /* steps 14-15 */ boolean newWritable; if (!newLenDesc.hasWritable() || newLenDesc.isWritable()) { newWritable = true; } else { newWritable = false; newLenDesc.setWritable(true); } /* steps 16-17 */ boolean succeeded = array.defineLength(newLenDesc, newLen); /* step 18 */ if (!succeeded) { return false; } /* step 19 */ long nonDeletableIndex = array.deleteRange(newLen, oldLen); /* step 19.d */ if (nonDeletableIndex >= 0) { array.length = nonDeletableIndex + 1; if (!newWritable) { array.lengthWritable = false; } return false; } /* step 20 */ if (!newWritable) { array.lengthWritable = false; } /* step 21 */ return true; } /** * 9.4.2.4 ArraySetLength(A, Desc) * * @param cx * the execution context * @param array * the array object * @param lenValue * the new length value * @return {@code true} on success */ private static boolean ArraySetLength(ExecutionContext cx, ArrayObject array, Object lenValue) { /* steps 1-2 (not applicable) */ /* steps 3-4 */ long newLen = ToUint32(cx, lenValue); /* steps 5-6 */ double numberLen = ToNumber(cx, lenValue); /* step 7 */ if (newLen != numberLen) { throw newRangeError(cx, Messages.Key.InvalidArrayLength); } /* step 8 (not applicable) */ /* steps 9-11 */ long oldLen = array.length; /* step 12 */ if (newLen >= oldLen) { return array.defineLength(newLen); } /* step 13 */ if (!array.lengthWritable) { return false; } /* steps 14-15 (not applicable) */ /* steps 16-17 */ boolean succeeded = array.defineLength(newLen); /* step 18 */ assert succeeded; /* step 19 */ long nonDeletableIndex = array.deleteRange(newLen, oldLen); /* step 19.d */ if (nonDeletableIndex >= 0) { array.length = nonDeletableIndex + 1; return false; } /* step 20 (not applicable) */ /* step 21 */ return true; } /** * Inserts the object into this array object. * * @param index * the destination start index * @param source * the source object * @param sourceLength * the source object's length */ public final void insertFrom(int index, OrdinaryObject source, long sourceLength) { assert isExtensible() && lengthWritable && index >= this.length; assert source.isDenseArray(sourceLength); assert 0 <= sourceLength && sourceLength <= Integer.MAX_VALUE : "length=" + sourceLength; assert index + sourceLength <= Integer.MAX_VALUE; int len = (int) sourceLength; for (int i = 0, j = index; i < len; ++i, ++j) { setIndexed(j, source.getIndexed(i)); } this.length = index + len; } /** * Inserts the object into this array object. * * @param cx * the execution context * @param index * the destination start index * @param source * the source object */ public final void insertFrom(ExecutionContext cx, int index, TypedArrayObject source) { assert isExtensible() && lengthWritable && index >= this.length; assert !source.getBuffer().isDetached(); long sourceLength = source.getLength(); assert 0 <= sourceLength && sourceLength <= Integer.MAX_VALUE : "length=" + sourceLength; assert index + sourceLength <= Integer.MAX_VALUE; int len = (int) sourceLength; for (int i = 0, j = index; i < len; ++i, ++j) { setIndexed(j, source.get(cx, i, source)); } this.length = index + len; } /** * Inserts the value into this array object. * * @param index * the array index * @param value * the value */ public final void insert(int index, Object value) { assert isExtensible() && lengthWritable && index >= getIndexedLength(); setIndexed(index, value); if (index >= length) { this.length = index + 1; } } }