src/jdk/nashorn/internal/objects/NativeArray.java

Mon, 11 Feb 2013 21:26:06 +0530

author
sundar
date
Mon, 11 Feb 2013 21:26:06 +0530
changeset 82
abea4ba28901
parent 44
e62dba3ce52b
child 112
267cc4c85160
permissions
-rw-r--r--

8007915: Nashorn IR, codegen, parser packages and Context instance should be inaccessible to user code
Reviewed-by: lagergren, jlaskey, attila

     1 /*
     2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    26 package jdk.nashorn.internal.objects;
    28 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
    29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
    30 import static jdk.nashorn.internal.runtime.PropertyDescriptor.VALUE;
    31 import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE;
    32 import static jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator.arrayLikeIterator;
    33 import static jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator.reverseArrayLikeIterator;
    35 import java.lang.invoke.MethodHandle;
    36 import java.util.ArrayList;
    37 import java.util.Arrays;
    38 import java.util.Collections;
    39 import java.util.Comparator;
    40 import java.util.Iterator;
    41 import java.util.List;
    42 import jdk.nashorn.internal.objects.annotations.Attribute;
    43 import jdk.nashorn.internal.objects.annotations.Constructor;
    44 import jdk.nashorn.internal.objects.annotations.Function;
    45 import jdk.nashorn.internal.objects.annotations.Getter;
    46 import jdk.nashorn.internal.objects.annotations.ScriptClass;
    47 import jdk.nashorn.internal.objects.annotations.Setter;
    48 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
    49 import jdk.nashorn.internal.objects.annotations.Where;
    50 import jdk.nashorn.internal.runtime.JSType;
    51 import jdk.nashorn.internal.runtime.PropertyDescriptor;
    52 import jdk.nashorn.internal.runtime.ScriptFunction;
    53 import jdk.nashorn.internal.runtime.ScriptObject;
    54 import jdk.nashorn.internal.runtime.ScriptRuntime;
    55 import jdk.nashorn.internal.runtime.Undefined;
    56 import jdk.nashorn.internal.runtime.arrays.ArrayData;
    57 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
    58 import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
    59 import jdk.nashorn.internal.runtime.arrays.IteratorAction;
    60 import jdk.nashorn.internal.runtime.linker.Bootstrap;
    61 import jdk.nashorn.internal.runtime.linker.InvokeByName;
    63 /**
    64  * Runtime representation of a JavaScript array. NativeArray only holds numeric
    65  * keyed values. All other values are stored in spill.
    66  */
    67 @ScriptClass("Array")
    68 public final class NativeArray extends ScriptObject {
    69     private static final InvokeByName JOIN = new InvokeByName("join", ScriptObject.class);
    71     private static final MethodHandle EVERY_CALLBACK_INVOKER   = createIteratorCallbackInvoker(boolean.class);
    72     private static final MethodHandle SOME_CALLBACK_INVOKER    = createIteratorCallbackInvoker(boolean.class);
    73     private static final MethodHandle FOREACH_CALLBACK_INVOKER = createIteratorCallbackInvoker(void.class);
    74     private static final MethodHandle MAP_CALLBACK_INVOKER     = createIteratorCallbackInvoker(Object.class);
    75     private static final MethodHandle FILTER_CALLBACK_INVOKER  = createIteratorCallbackInvoker(boolean.class);
    77     private static final MethodHandle REDUCE_CALLBACK_INVOKER = Bootstrap.createDynamicInvoker("dyn:call", Object.class,
    78             Object.class, Undefined.class, Object.class, Object.class, int.class, Object.class);
    79     private static final MethodHandle CALL_CMP                = Bootstrap.createDynamicInvoker("dyn:call", int.class,
    80             ScriptFunction.class, Object.class, Object.class, Object.class);
    82     private static final InvokeByName TO_LOCALE_STRING = new InvokeByName("toLocaleString", ScriptObject.class, String.class);
    85     /*
    86      * Constructors.
    87      */
    88     NativeArray() {
    89         this(ArrayData.initialArray());
    90     }
    92     NativeArray(final long length) {
    93         // TODO assert valid index in long before casting
    94         this(ArrayData.allocate((int) length));
    95     }
    97     NativeArray(final int[] array) {
    98         this(ArrayData.allocate(array));
    99     }
   101     NativeArray(final long[] array) {
   102         this(ArrayData.allocate(array));
   103     }
   105     NativeArray(final double[] array) {
   106         this(ArrayData.allocate(array));
   107     }
   109     NativeArray(final Object[] array) {
   110         this(ArrayData.allocate(array.length));
   112         ArrayData arrayData = this.getArray();
   113         arrayData.ensure(array.length - 1);
   115         for (int index = 0; index < array.length; index++) {
   116             final Object value = array[index];
   118             if (value == ScriptRuntime.EMPTY) {
   119                 arrayData = arrayData.delete(index);
   120             } else {
   121                 arrayData = arrayData.set(index, value, isStrictContext());
   122             }
   123         }
   125         this.setArray(arrayData);
   126     }
   128     private NativeArray(final ArrayData arrayData) {
   129         setProto(Global.instance().getArrayPrototype());
   130         this.setArray(arrayData);
   131         this.setIsArray();
   132     }
   134     @Override
   135     public String getClassName() {
   136         return "Array";
   137     }
   139     @Override
   140     public Object getLength() {
   141         return getArray().length() & JSType.MAX_UINT;
   142     }
   144     /**
   145      * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw )
   146      */
   147     @Override
   148     public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) {
   149         final PropertyDescriptor desc = toPropertyDescriptor(Global.instance(), propertyDesc);
   151         // never be undefined as "length" is always defined and can't be deleted for arrays
   152         // Step 1
   153         final PropertyDescriptor oldLenDesc = (PropertyDescriptor) super.getOwnPropertyDescriptor("length");
   155         // Step 2
   156         // get old length and convert to long
   157         long oldLen = NativeArray.validLength(oldLenDesc.getValue(), true);
   159         // Step 3
   160         if ("length".equals(key)) {
   161             // Step 3a
   162             if (!desc.has(VALUE)) {
   163                 return super.defineOwnProperty("length", propertyDesc, reject);
   164             }
   166             // Step 3b
   167             final PropertyDescriptor newLenDesc = desc;
   169             // Step 3c and 3d - get new length and convert to long
   170             final long newLen = NativeArray.validLength(newLenDesc.getValue(), true);
   172             // Step 3e
   173             newLenDesc.setValue(newLen);
   175             // Step 3f
   176             // increasing array length - just need to set new length value (and attributes if any) and return
   177             if (newLen >= oldLen) {
   178                 return super.defineOwnProperty("length", newLenDesc, reject);
   179             }
   181             // Step 3g
   182             if (!oldLenDesc.isWritable()) {
   183                 if (reject) {
   184                     typeError("property.not.writable", "length", ScriptRuntime.safeToString(this));
   185                 }
   186                 return false;
   187             }
   189             // Step 3h and 3i
   190             final boolean newWritable = (!newLenDesc.has(WRITABLE) || newLenDesc.isWritable());
   191             if (!newWritable) {
   192                 newLenDesc.setWritable(true);
   193             }
   195             // Step 3j and 3k
   196             final boolean succeeded = super.defineOwnProperty("length", newLenDesc, reject);
   197             if (!succeeded) {
   198                 return false;
   199             }
   201             // Step 3l
   202             // make sure that length is set till the point we can delete the old elements
   203             while (newLen < oldLen) {
   204                 oldLen--;
   205                 final boolean deleteSucceeded = delete(oldLen, false);
   206                 if (!deleteSucceeded) {
   207                     newLenDesc.setValue(oldLen + 1);
   208                     if (!newWritable) {
   209                         newLenDesc.setWritable(false);
   210                     }
   211                     super.defineOwnProperty("length", newLenDesc, false);
   212                     if (reject) {
   213                         typeError("property.not.writable", "length", ScriptRuntime.safeToString(this));
   214                     }
   215                     return false;
   216                 }
   217             }
   219             // Step 3m
   220             if (!newWritable) {
   221                 // make 'length' property not writable
   222                 final ScriptObject newDesc = Global.newEmptyInstance();
   223                 newDesc.set(WRITABLE, false, false);
   224                 return super.defineOwnProperty("length", newDesc, false);
   225             }
   227             return true;
   228         }
   230         // Step 4a
   231         final int index = ArrayIndex.getArrayIndexNoThrow(key);
   232         if (ArrayIndex.isValidArrayIndex(index)) {
   233             final long longIndex = ArrayIndex.toLongIndex(index);
   234             // Step 4b
   235             // setting an element beyond current length, but 'length' is not writable
   236             if (longIndex >= oldLen && !oldLenDesc.isWritable()) {
   237                 if (reject) {
   238                     typeError("property.not.writable", Long.toString(longIndex), ScriptRuntime.safeToString(this));
   239                 }
   240                 return false;
   241             }
   243             // Step 4c
   244             // set the new array element
   245             final boolean succeeded = super.defineOwnProperty(key, propertyDesc, false);
   247             // Step 4d
   248             if (!succeeded) {
   249                 if (reject) {
   250                     typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this));
   251                 }
   252                 return false;
   253             }
   255             // Step 4e -- adjust new length based on new element index that is set
   256             if (longIndex >= oldLen) {
   257                 oldLenDesc.setValue(longIndex + 1);
   258                 super.defineOwnProperty("length", oldLenDesc, false);
   259             }
   261             // Step 4f
   262             return true;
   263         }
   265         // not an index property
   266         return super.defineOwnProperty(key, propertyDesc, reject);
   267     }
   269     /**
   270      * Return the array contents upcasted as an ObjectArray, regardless of
   271      * representation
   272      *
   273      * @return an object array
   274      */
   275     public Object[] asObjectArray() {
   276         return getArray().asObjectArray();
   277     }
   279     /**
   280      * ECMA 15.4.3.2 Array.isArray ( arg )
   281      *
   282      * @param self self reference
   283      * @param arg  argument - object to check
   284      * @return true if argument is an array
   285      */
   286     @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
   287     public static Object isArray(final Object self, final Object arg) {
   288         return isArray(arg) || (arg == Global.instance().getArrayPrototype())
   289                 || (arg instanceof NativeRegExpExecResult);
   290     }
   292     /**
   293      * Length getter
   294      * @param self self reference
   295      * @return the length of the object
   296      */
   297     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
   298     public static Object length(final Object self) {
   299         if (isArray(self)) {
   300             return ((NativeArray) self).getArray().length() & JSType.MAX_UINT;
   301         }
   303         return 0;
   304     }
   306     /**
   307      * Length setter
   308      * @param self   self reference
   309      * @param length new length property
   310      */
   311     @Setter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
   312     public static void length(final Object self, final Object length) {
   313         if (isArray(self)) {
   314             ((NativeArray) self).setLength(validLength(length, true));
   315         }
   316     }
   318     static long validLength(final Object length, final boolean reject) {
   319         final double doubleLength = JSType.toNumber(length);
   320         if (!Double.isNaN(doubleLength) && JSType.isRepresentableAsLong(doubleLength)) {
   321             final long len = (long) doubleLength;
   322             if (len >= 0 && len <= JSType.MAX_UINT) {
   323                 return len;
   324             }
   325         }
   326         if (reject) {
   327             rangeError("inappropriate.array.length", ScriptRuntime.safeToString(length));
   328         }
   329         return -1;
   330     }
   332     /**
   333      * ECMA 15.4.4.2 Array.prototype.toString ( )
   334      *
   335      * @param self self reference
   336      * @return string representation of array
   337      */
   338     @Function(attributes = Attribute.NOT_ENUMERABLE)
   339     public static Object toString(final Object self) {
   340         if (self instanceof ScriptObject) {
   341             final ScriptObject sobj = (ScriptObject) self;
   342             try {
   343                 final Object join = JOIN.getGetter().invokeExact(sobj);
   344                 if (join instanceof ScriptFunction) {
   345                     return JOIN.getInvoker().invokeExact(join, sobj);
   346                 }
   347             } catch (final RuntimeException | Error e) {
   348                 throw e;
   349             } catch (final Throwable t) {
   350                 throw new RuntimeException(t);
   351             }
   352         }
   354         // FIXME: should lookup Object.prototype.toString and call that?
   355         return ScriptRuntime.builtinObjectToString(self);
   356     }
   358     /**
   359      * ECMA 15.4.4.3 Array.prototype.toLocaleString ( )
   360      *
   361      * @param self self reference
   362      * @return locale specific string representation for array
   363      */
   364     @Function(attributes = Attribute.NOT_ENUMERABLE)
   365     public static Object toLocaleString(final Object self) {
   366         final StringBuilder sb = new StringBuilder();
   367         final Iterator<Object> iter = arrayLikeIterator(self, true);
   369         while (iter.hasNext()) {
   370             final Object obj = iter.next();
   372             if (obj != null && obj != ScriptRuntime.UNDEFINED) {
   373                 final Object val = JSType.toScriptObject(obj);
   375                 try {
   376                     if (val instanceof ScriptObject) {
   377                         final ScriptObject sobj           = (ScriptObject)val;
   378                         final Object       toLocaleString = TO_LOCALE_STRING.getGetter().invokeExact(sobj);
   380                         if (toLocaleString instanceof ScriptFunction) {
   381                             sb.append((String)TO_LOCALE_STRING.getInvoker().invokeExact(toLocaleString, sobj));
   382                         } else {
   383                             typeError("not.a.function", "toLocaleString");
   384                         }
   385                     }
   386                 } catch (final Error|RuntimeException t) {
   387                     throw t;
   388                 } catch (final Throwable t) {
   389                     throw new RuntimeException(t);
   390                 }
   391             }
   393             if (iter.hasNext()) {
   394                 sb.append(",");
   395             }
   396         }
   398         return sb.toString();
   399     }
   401     /**
   402      * ECMA 15.4.2.2 new Array (len)
   403      *
   404      * @param newObj was the new operator used to instantiate this array
   405      * @param self   self reference
   406      * @param args   arguments (length)
   407      * @return the new NativeArray
   408      */
   409     @Constructor(arity = 1)
   410     public static Object construct(final boolean newObj, final Object self, final Object... args) {
   411         switch (args.length) {
   412         case 0:
   413             return new NativeArray(0);
   414         case 1:
   415             final Object len = args[0];
   416             if (len instanceof Number) {
   417                 long length;
   418                 if (len instanceof Integer || len instanceof Long) {
   419                     length = ((Number) len).longValue();
   420                     if (length >= 0 && length < 0xffff_ffffL) {
   421                         return new NativeArray(length);
   422                     }
   423                 }
   425                 length = JSType.toUint32(len);
   427                 /*
   428                  * If the argument len is a Number and ToUint32(len) is equal to
   429                  * len, then the length property of the newly constructed object
   430                  * is set to ToUint32(len). If the argument len is a Number and
   431                  * ToUint32(len) is not equal to len, a RangeError exception is
   432                  * thrown.
   433                  */
   434                 final double numberLength = ((Number) len).doubleValue();
   435                 if (length != numberLength) {
   436                     rangeError("inappropriate.array.length", JSType.toString(numberLength));
   437                 }
   439                 return new NativeArray(length);
   440             }
   441             /*
   442              * If the argument len is not a Number, then the length property of
   443              * the newly constructed object is set to 1 and the 0 property of
   444              * the newly constructed object is set to len
   445              */
   446             return new NativeArray(new Object[]{args[0]});
   447             //fallthru
   448         default:
   449             return new NativeArray(args);
   450         }
   451     }
   453     /**
   454      * ECMA 15.4.2.2 new Array (len)
   455      *
   456      * Specialized constructor for zero arguments - empty array
   457      *
   458      * @param newObj was the new operator used to instantiate this array
   459      * @param self   self reference
   460      * @return the new NativeArray
   461      */
   462     @SpecializedConstructor
   463     public static Object construct(final boolean newObj, final Object self) {
   464         return new NativeArray(0);
   465     }
   467     /**
   468      * ECMA 15.4.2.2 new Array (len)
   469      *
   470      * Specialized constructor for one integer argument (length)
   471      *
   472      * @param newObj was the new operator used to instantiate this array
   473      * @param self   self reference
   474      * @param length array length
   475      * @return the new NativeArray
   476      */
   477     @SpecializedConstructor
   478     public static Object construct(final boolean newObj, final Object self, final int length) {
   479         if (length >= 0) {
   480             return new NativeArray(length);
   481         }
   483         return construct(newObj, self, new Object[]{length});
   484     }
   486     /**
   487      * ECMA 15.4.2.2 new Array (len)
   488      *
   489      * Specialized constructor for one long argument (length)
   490      *
   491      * @param newObj was the new operator used to instantiate this array
   492      * @param self   self reference
   493      * @param length array length
   494      * @return the new NativeArray
   495      */
   496     @SpecializedConstructor
   497     public static Object construct(final boolean newObj, final Object self, final long length) {
   498         if (length >= 0L && length <= JSType.MAX_UINT) {
   499             return new NativeArray(length);
   500         }
   502         return construct(newObj, self, new Object[]{length});
   503     }
   505     /**
   506      * ECMA 15.4.2.2 new Array (len)
   507      *
   508      * Specialized constructor for one double argument (length)
   509      *
   510      * @param newObj was the new operator used to instantiate this array
   511      * @param self   self reference
   512      * @param length array length
   513      * @return the new NativeArray
   514      */
   515     @SpecializedConstructor
   516     public static Object construct(final boolean newObj, final Object self, final double length) {
   517         final long uint32length = JSType.toUint32(length);
   519         if (uint32length == length) {
   520             return new NativeArray(uint32length);
   521         }
   523         return construct(newObj, self, new Object[]{length});
   524     }
   526     /**
   527      * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
   528      *
   529      * @param self self reference
   530      * @param args arguments to concat
   531      * @return resulting NativeArray
   532      */
   533     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
   534     public static Object concat(final Object self, final Object... args) {
   535         final ArrayList<Object> list = new ArrayList<>();
   536         final Object selfToObject = Global.toObject(self);
   538         if (isArray(selfToObject)) {
   539             final Iterator<Object> iter = arrayLikeIterator(selfToObject, true);
   540             while (iter.hasNext()) {
   541                 list.add(iter.next());
   542             }
   543         } else {
   544             // single element, add it
   545             list.add(selfToObject);
   546         }
   548         for (final Object obj : args) {
   549             if (isArray(obj) || obj instanceof Iterable || (obj != null && obj.getClass().isArray())) {
   550                 final Iterator<Object> iter = arrayLikeIterator(obj, true);
   551                 if (iter.hasNext()) {
   552                     while (iter.hasNext()) {
   553                         list.add(iter.next());
   554                     }
   555                 } else if (!isArray(obj)) {
   556                     list.add(obj); // add empty object, but not an empty array
   557                 }
   558             } else {
   559                 // single element, add it
   560                 list.add(obj);
   561             }
   562         }
   564         return new NativeArray(list.toArray());
   565     }
   567     /**
   568      * ECMA 15.4.4.5 Array.prototype.join (separator)
   569      *
   570      * @param self      self reference
   571      * @param separator element separator
   572      * @return string representation after join
   573      */
   574     @Function(attributes = Attribute.NOT_ENUMERABLE)
   575     public static Object join(final Object self, final Object separator) {
   576         final String           sep  = separator == ScriptRuntime.UNDEFINED ? "," : JSType.toString(separator);
   577         final StringBuilder    sb   = new StringBuilder();
   578         final Iterator<Object> iter = arrayLikeIterator(self, true);
   580         while (iter.hasNext()) {
   581             final Object obj = iter.next();
   583             if (obj != null && obj != ScriptRuntime.UNDEFINED) {
   584                 sb.append(JSType.toString(obj));
   585             }
   587             if (iter.hasNext()) {
   588                 sb.append(sep);
   589             }
   590         }
   592         return sb.toString();
   593     }
   595     /**
   596      * ECMA 15.4.4.6 Array.prototype.pop ()
   597      *
   598      * @param self self reference
   599      * @return array after pop
   600      */
   601     @Function(attributes = Attribute.NOT_ENUMERABLE)
   602     public static Object pop(final Object self) {
   603         try {
   604             final ScriptObject sobj = (ScriptObject)self;
   605             final boolean strict    = sobj.isStrictContext();
   607             if (bulkable(sobj)) {
   608                 return ((NativeArray)sobj).getArray().pop();
   609             }
   611             final long len = JSType.toUint32(sobj.getLength());
   613             if (len == 0) {
   614                 sobj.set("length", 0, strict);
   615                 return ScriptRuntime.UNDEFINED;
   616             }
   618             final long   index   = len - 1;
   619             final Object element = sobj.get(index);
   621             sobj.delete(index, strict);
   622             sobj.set("length", index, strict);
   624             return element;
   625         } catch (final ClassCastException | NullPointerException e) {
   626             typeError("not.an.object", ScriptRuntime.safeToString(self));
   627             return ScriptRuntime.UNDEFINED;
   628         }
   629     }
   631     /**
   632      * ECMA 15.4.4.7 Array.prototype.push (args...)
   633      *
   634      * @param self self reference
   635      * @param args arguments to push
   636      * @return array after pushes
   637      */
   638     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
   639     public static Object push(final Object self, final Object... args) {
   640         try {
   641             final ScriptObject sobj   = (ScriptObject)self;
   642             final boolean      strict = sobj.isStrictContext();
   644             if (bulkable(sobj)) {
   645                 final NativeArray nativeArray = (NativeArray)sobj;
   646                 if (nativeArray.getArray().length() + args.length <= JSType.MAX_UINT) {
   647                     final ArrayData newData = nativeArray.getArray().push(nativeArray.isStrictContext(), args);
   648                     nativeArray.setArray(newData);
   649                     return newData.length();
   650                 }
   651                 //fallthru
   652             }
   654             long len = JSType.toUint32(sobj.getLength());
   655             for (final Object element : args) {
   656                 sobj.set(len++, element, strict);
   657             }
   658             sobj.set("length", len, strict);
   660             return len;
   661         } catch (final ClassCastException | NullPointerException e) {
   662             typeError("not.an.object", ScriptRuntime.safeToString(self));
   663             return ScriptRuntime.UNDEFINED;
   664         }
   665     }
   667     /**
   668      * ECMA 15.4.4.8 Array.prototype.reverse ()
   669      *
   670      * @param self self reference
   671      * @return reversed array
   672      */
   673     @Function(attributes = Attribute.NOT_ENUMERABLE)
   674     public static Object reverse(final Object self) {
   675         try {
   676             final ScriptObject sobj   = (ScriptObject)self;
   677             final boolean      strict = sobj.isStrictContext();
   678             final long         len    = JSType.toUint32(sobj.getLength());
   679             final long         middle = len / 2;
   681             for (long lower = 0; lower != middle; lower++) {
   682                 final long    upper       = len - lower - 1;
   683                 final Object  lowerValue  = sobj.get(lower);
   684                 final Object  upperValue  = sobj.get(upper);
   685                 final boolean lowerExists = sobj.has(lower);
   686                 final boolean upperExists = sobj.has(upper);
   688                 if (lowerExists && upperExists) {
   689                     sobj.set(lower, upperValue, strict);
   690                     sobj.set(upper, lowerValue, strict);
   691                 } else if (!lowerExists && upperExists) {
   692                     sobj.set(lower, upperValue, strict);
   693                     sobj.delete(upper, strict);
   694                 } else if (lowerExists && !upperExists) {
   695                     sobj.delete(lower, strict);
   696                     sobj.set(upper, lowerValue, strict);
   697                 }
   698             }
   699             return sobj;
   700         } catch (final ClassCastException | NullPointerException e) {
   701             typeError("not.an.object", ScriptRuntime.safeToString(self));
   702             return ScriptRuntime.UNDEFINED;
   703         }
   704     }
   706     /**
   707      * ECMA 15.4.4.9 Array.prototype.shift ()
   708      *
   709      * @param self self reference
   710      * @return shifted array
   711      */
   712     @Function(attributes = Attribute.NOT_ENUMERABLE)
   713     public static Object shift(final Object self) {
   714         final Object obj = Global.toObject(self);
   716         Object first = ScriptRuntime.UNDEFINED;
   718         if (!(obj instanceof ScriptObject)) {
   719             return first;
   720         }
   722         final ScriptObject sobj   = (ScriptObject) obj;
   723         final boolean      strict = Global.isStrict();
   725         long len = JSType.toUint32(sobj.getLength());
   727         if (len > 0) {
   728             first = sobj.get(0);
   730             if (bulkable(sobj)) {
   731                 ((NativeArray) sobj).getArray().shiftLeft(1);
   732             } else {
   733                 for (long k = 1; k < len; k++) {
   734                     sobj.set(k - 1, sobj.get(k), strict);
   735                 }
   736             }
   737             sobj.delete(--len, strict);
   738         } else {
   739             len = 0;
   740         }
   742         sobj.set("length", len, strict);
   744         return first;
   745     }
   747     /**
   748      * ECMA 15.4.4.10 Array.prototype.slice ( start [ , end ] )
   749      *
   750      * @param self  self reference
   751      * @param start start of slice (inclusive)
   752      * @param end   end of slice (optional, exclusive)
   753      * @return sliced array
   754      */
   755     @Function(attributes = Attribute.NOT_ENUMERABLE)
   756     public static Object slice(final Object self, final Object start, final Object end) {
   757         final Object       obj                 = Global.toObject(self);
   758         final ScriptObject sobj                = (ScriptObject)obj;
   759         final long         len                 = JSType.toUint32(sobj.getLength());
   760         final long         relativeStartUint32 = JSType.toUint32(start);
   761         final long         relativeStart       = JSType.toInteger(start);
   763         long k = relativeStart < 0 ?
   764                 Math.max(len + relativeStart, 0) :
   765                 Math.min(
   766                     Math.max(relativeStartUint32, relativeStart),
   767                     len);
   769         final long relativeEndUint32 = end == ScriptRuntime.UNDEFINED ? len : JSType.toUint32(end);
   770         final long relativeEnd       = end == ScriptRuntime.UNDEFINED ? len : JSType.toInteger(end);
   772         final long finale = relativeEnd < 0 ?
   773                 Math.max(len + relativeEnd, 0) :
   774                 Math.min(
   775                     Math.max(relativeEndUint32, relativeEnd),
   776                     len);
   778         if (k >= finale) {
   779             return new NativeArray(0);
   780         }
   782         if (bulkable(sobj)) {
   783             final NativeArray narray = (NativeArray) sobj;
   784             return new NativeArray(narray.getArray().slice(k, finale));
   785         }
   787         final NativeArray copy = new NativeArray(0);
   788         for (long n = 0; k < finale; n++, k++) {
   789             copy.defineOwnProperty((int) n, sobj.get(k));
   790         }
   792         return copy;
   793     }
   795     private static ScriptFunction compareFunction(final Object comparefn) {
   796         try {
   797             return (ScriptFunction)comparefn;
   798         } catch (final ClassCastException e) {
   799             return null; //undefined or null
   800         }
   801     }
   803     private static Object[] sort(final Object[] array, final Object comparefn) {
   804         final ScriptFunction cmp = compareFunction(comparefn);
   806         final List<Object> list = Arrays.asList(array);
   807         final Object cmpThis = cmp == null || cmp.isStrict() ? ScriptRuntime.UNDEFINED : Global.instance();
   809         Collections.sort(list, new Comparator<Object>() {
   810             @Override
   811             public int compare(final Object x, final Object y) {
   812                 if (x == ScriptRuntime.UNDEFINED && y == ScriptRuntime.UNDEFINED) {
   813                     return 0;
   814                 } else if (x == ScriptRuntime.UNDEFINED) {
   815                     return 1;
   816                 } else if (y == ScriptRuntime.UNDEFINED) {
   817                     return -1;
   818                 }
   820                 if (cmp != null) {
   821                     try {
   822                         return (int)CALL_CMP.invokeExact(cmp, cmpThis, x, y);
   823                     } catch (final RuntimeException | Error e) {
   824                         throw e;
   825                     } catch (final Throwable t) {
   826                         throw new RuntimeException(t);
   827                     }
   828                 }
   830                 return JSType.toString(x).compareTo(JSType.toString(y));
   831             }
   832         });
   834         return list.toArray(new Object[array.length]);
   835     }
   837     /**
   838      * ECMA 15.4.4.11 Array.prototype.sort ( comparefn )
   839      *
   840      * @param self       self reference
   841      * @param comparefn  element comparison function
   842      * @return sorted array
   843      */
   844     @Function(attributes = Attribute.NOT_ENUMERABLE)
   845     public static Object sort(final Object self, final Object comparefn) {
   846         try {
   847             final ScriptObject sobj    = (ScriptObject) self;
   848             final boolean      strict  = sobj.isStrictContext();
   849             final long         len     = JSType.toUint32(sobj.getLength());
   851             if (len > 1) {
   852                 final Object[] src = new Object[(int) len];
   853                 for (int i = 0; i < src.length; i++) {
   854                     src[i] = sobj.get(i);
   855                 }
   857                 final Object[] sorted = sort(src, comparefn);
   858                 assert sorted.length == src.length;
   860                 for (int i = 0; i < sorted.length; i++) {
   861                     sobj.set(i, sorted[i], strict);
   862                 }
   863             }
   865             return sobj;
   866         } catch (final ClassCastException | NullPointerException e) {
   867             typeError("not.an.object", ScriptRuntime.safeToString(self));
   868             return ScriptRuntime.UNDEFINED;
   869         }
   870     }
   872     /**
   873      * ECMA 15.4.4.12 Array.prototype.splice ( start, deleteCount [ item1 [ , item2 [ , ... ] ] ] )
   874      *
   875      * @param self self reference
   876      * @param args arguments
   877      * @return result of splice
   878      */
   879     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
   880     public static Object splice(final Object self, final Object... args) {
   881         final Object obj = Global.toObject(self);
   883         if (!(obj instanceof ScriptObject)) {
   884             return ScriptRuntime.UNDEFINED;
   885         }
   887         final Object start = (args.length > 0) ? args[0] : ScriptRuntime.UNDEFINED;
   888         final Object deleteCount = (args.length > 1) ? args[1] : ScriptRuntime.UNDEFINED;
   890         Object[] items;
   892         if (args.length > 2) {
   893             items = new Object[args.length - 2];
   894             System.arraycopy(args, 2, items, 0, items.length);
   895         } else {
   896             items = ScriptRuntime.EMPTY_ARRAY;
   897         }
   899         final ScriptObject sobj                = (ScriptObject)obj;
   900         final boolean      strict              = Global.isStrict();
   901         final long         len                 = JSType.toUint32(sobj.getLength());
   902         final long         relativeStartUint32 = JSType.toUint32(start);
   903         final long         relativeStart       = JSType.toInteger(start);
   905         //TODO: workaround overflow of relativeStart for start > Integer.MAX_VALUE
   906         final long actualStart = relativeStart < 0 ?
   907             Math.max(len + relativeStart, 0) :
   908             Math.min(
   909                 Math.max(relativeStartUint32, relativeStart),
   910                 len);
   912         final long actualDeleteCount =
   913             Math.min(
   914                 Math.max(JSType.toInteger(deleteCount), 0),
   915                 len - actualStart);
   917         final NativeArray array = new NativeArray(actualDeleteCount);
   919         for (long k = 0; k < actualDeleteCount; k++) {
   920             final long from = actualStart + k;
   922             if (sobj.has(from)) {
   923                 array.defineOwnProperty((int) k, sobj.get(from));
   924             }
   925         }
   927         if (items.length < actualDeleteCount) {
   928             for (long k = actualStart; k < (len - actualDeleteCount); k++) {
   929                 final long from = k + actualDeleteCount;
   930                 final long to   = k + items.length;
   932                 if (sobj.has(from)) {
   933                     sobj.set(to, sobj.get(from), strict);
   934                 } else {
   935                     sobj.delete(to, strict);
   936                 }
   937             }
   939             for (long k = len; k > (len - actualDeleteCount + items.length); k--) {
   940                 sobj.delete(k - 1, strict);
   941             }
   942         } else if (items.length > actualDeleteCount) {
   943             for (long k = len - actualDeleteCount; k > actualStart; k--) {
   944                 final long from = k + actualDeleteCount - 1;
   945                 final long to   = k + items.length - 1;
   947                 if (sobj.has(from)) {
   948                     final Object fromValue = sobj.get(from);
   949                     sobj.set(to, fromValue, strict);
   950                 } else {
   951                     sobj.delete(to, strict);
   952                 }
   953             }
   954         }
   956         long k = actualStart;
   957         for (int i = 0; i < items.length; i++, k++) {
   958             sobj.set(k, items[i], strict);
   959         }
   961         final long newLength = len - actualDeleteCount + items.length;
   962         sobj.set("length", newLength, strict);
   964         return array;
   965     }
   967     /**
   968      * ECMA 15.4.4.13 Array.prototype.unshift ( [ item1 [ , item2 [ , ... ] ] ] )
   969      *
   970      * @param self  self reference
   971      * @param items items for unshift
   972      * @return unshifted array
   973      */
   974     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
   975     public static Object unshift(final Object self, final Object... items) {
   976         final Object obj = Global.toObject(self);
   978         if (!(obj instanceof ScriptObject)) {
   979             return ScriptRuntime.UNDEFINED;
   980         }
   982         final ScriptObject sobj   = (ScriptObject)obj;
   983         final boolean      strict = Global.isStrict();
   984         final long         len    = JSType.toUint32(sobj.getLength());
   986         if (items == null) {
   987             return ScriptRuntime.UNDEFINED;
   988         }
   990         if (bulkable(sobj)) {
   991             final NativeArray nativeArray = (NativeArray) sobj;
   992             nativeArray.getArray().shiftRight(items.length);
   994             for (int j = 0; j < items.length; j++) {
   995                 nativeArray.setArray(nativeArray.getArray().set(j, items[j], sobj.isStrictContext()));
   996             }
   997         } else {
   998             for (long k = len; k > 0; k--) {
   999                 final long from = k - 1;
  1000                 final long to = k + items.length - 1;
  1002                 if (sobj.has(from)) {
  1003                     final Object fromValue = sobj.get(from);
  1004                     sobj.set(to, fromValue, strict);
  1005                 } else {
  1006                     sobj.delete(to, strict);
  1010             for (int j = 0; j < items.length; j++) {
  1011                 sobj.set(j, items[j], strict);
  1015         final long newLength = len + items.length;
  1016         sobj.set("length", newLength, strict);
  1018         return newLength;
  1021     /**
  1022      * ECMA 15.4.4.14 Array.prototype.indexOf ( searchElement [ , fromIndex ] )
  1024      * @param self           self reference
  1025      * @param searchElement  element to search for
  1026      * @param fromIndex      start index of search
  1027      * @return index of element, or -1 if not found
  1028      */
  1029     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1030     public static Object indexOf(final Object self, final Object searchElement, final Object fromIndex) {
  1031         try {
  1032             final ScriptObject sobj = (ScriptObject)Global.toObject(self);
  1033             final long         len  = JSType.toUint32(sobj.getLength());
  1034             final long         n    = JSType.toLong(fromIndex);
  1036             if (len == 0 || n >= len) {
  1037                 return -1;
  1040             for (long k = Math.max(0, (n < 0) ? (len - Math.abs(n)) : n); k < len; k++) {
  1041                 if (sobj.has(k)) {
  1042                     if (ScriptRuntime.EQ_STRICT(sobj.get(k), searchElement)) {
  1043                         return k;
  1047         } catch (final ClassCastException | NullPointerException e) {
  1048             //fallthru
  1051         return -1;
  1054     /**
  1055      * ECMA 15.4.4.15 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )
  1057      * @param self self reference
  1058      * @param args arguments: element to search for and optional from index
  1059      * @return index of element, or -1 if not found
  1060      */
  1061     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1062     public static Object lastIndexOf(final Object self, final Object... args) {
  1063         try {
  1064             final ScriptObject sobj = (ScriptObject)Global.toObject(self);
  1065             final long         len  = JSType.toUint32(sobj.getLength());
  1067             if (len == 0) {
  1068                 return -1;
  1071             final Object searchElement = (args.length > 0) ? args[0] : ScriptRuntime.UNDEFINED;
  1072             final long   n             = (args.length > 1) ? JSType.toLong(args[1]) : (len - 1);
  1074             for (long k = (n < 0) ? (len - Math.abs(n)) : Math.min(n, len - 1); k >= 0; k--) {
  1075                 if (sobj.has(k)) {
  1076                     if (ScriptRuntime.EQ_STRICT(sobj.get(k), searchElement)) {
  1077                         return k;
  1081         } catch (final ClassCastException | NullPointerException e) {
  1082             typeError("not.an.object", ScriptRuntime.safeToString(self));
  1085         return -1;
  1088     /**
  1089      * ECMA 15.4.4.16 Array.prototype.every ( callbackfn [ , thisArg ] )
  1091      * @param self        self reference
  1092      * @param callbackfn  callback function per element
  1093      * @param thisArg     this argument
  1094      * @return true if callback function return true for every element in the array, false otherwise
  1095      */
  1096     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1097     public static Object every(final Object self, final Object callbackfn, final Object thisArg) {
  1098         return applyEvery(Global.toObject(self), callbackfn, thisArg);
  1101     private static boolean applyEvery(final Object self, final Object callbackfn, final Object thisArg) {
  1102         return new IteratorAction<Boolean>(Global.toObject(self), callbackfn, thisArg, true) {
  1103             @Override
  1104             protected boolean forEach(final Object val, final int i) throws Throwable {
  1105                 return (result = (boolean)EVERY_CALLBACK_INVOKER.invokeExact(callbackfn, thisArg, val, i, self));
  1107         }.apply();
  1110     /**
  1111      * ECMA 15.4.4.17 Array.prototype.some ( callbackfn [ , thisArg ] )
  1113      * @param self        self reference
  1114      * @param callbackfn  callback function per element
  1115      * @param thisArg     this argument
  1116      * @return true if callback function returned true for any element in the array, false otherwise
  1117      */
  1118     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1119     public static Object some(final Object self, final Object callbackfn, final Object thisArg) {
  1120         return new IteratorAction<Boolean>(Global.toObject(self), callbackfn, thisArg, false) {
  1121             @Override
  1122             protected boolean forEach(final Object val, final int i) throws Throwable {
  1123                 return !(result = (boolean)SOME_CALLBACK_INVOKER.invokeExact(callbackfn, thisArg, val, i, self));
  1125         }.apply();
  1128     /**
  1129      * ECMA 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
  1131      * @param self        self reference
  1132      * @param callbackfn  callback function per element
  1133      * @param thisArg     this argument
  1134      * @return undefined
  1135      */
  1136     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1137     public static Object forEach(final Object self, final Object callbackfn, final Object thisArg) {
  1138         return new IteratorAction<Object>(Global.toObject(self), callbackfn, thisArg, ScriptRuntime.UNDEFINED) {
  1139             @Override
  1140             protected boolean forEach(final Object val, final int i) throws Throwable {
  1141                 FOREACH_CALLBACK_INVOKER.invokeExact(callbackfn, thisArg, val, i, self);
  1142                 return true;
  1144         }.apply();
  1147     /**
  1148      * ECMA 15.4.4.19 Array.prototype.map ( callbackfn [ , thisArg ] )
  1150      * @param self        self reference
  1151      * @param callbackfn  callback function per element
  1152      * @param thisArg     this argument
  1153      * @return array with elements transformed by map function
  1154      */
  1155     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1156     public static Object map(final Object self, final Object callbackfn, final Object thisArg) {
  1157         return new IteratorAction<NativeArray>(Global.toObject(self), callbackfn, thisArg, null) {
  1158             @Override
  1159             protected boolean forEach(final Object val, final int i) throws Throwable {
  1160                 final Object r = MAP_CALLBACK_INVOKER.invokeExact(callbackfn, thisArg, val, i, self);
  1161                 result.defineOwnProperty(index, r);
  1162                 return true;
  1165             @Override
  1166             public void applyLoopBegin(final ArrayLikeIterator<Object> iter0) {
  1167                 // map return array should be of same length as source array
  1168                 // even if callback reduces source array length
  1169                 result = new NativeArray(iter0.getLength());
  1171         }.apply();
  1174     /**
  1175      * ECMA 15.4.4.20 Array.prototype.filter ( callbackfn [ , thisArg ] )
  1177      * @param self        self reference
  1178      * @param callbackfn  callback function per element
  1179      * @param thisArg     this argument
  1180      * @return filtered array
  1181      */
  1182     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1183     public static Object filter(final Object self, final Object callbackfn, final Object thisArg) {
  1184         return new IteratorAction<NativeArray>(Global.toObject(self), callbackfn, thisArg, new NativeArray()) {
  1185             private int to = 0;
  1187             @Override
  1188             protected boolean forEach(final Object val, final int i) throws Throwable {
  1189                 if ((boolean)FILTER_CALLBACK_INVOKER.invokeExact(callbackfn, thisArg, val, i, self)) {
  1190                     result.defineOwnProperty(to++, val);
  1192                 return true;
  1194         }.apply();
  1197     private static Object reduceInner(final ArrayLikeIterator<Object> iter, final Object self, final Object... args) {
  1198         final Object  callbackfn          = args.length > 0 ? args[0] : ScriptRuntime.UNDEFINED;
  1199         final boolean initialValuePresent = args.length > 1;
  1201         Object initialValue = initialValuePresent ? args[1] : ScriptRuntime.UNDEFINED;
  1203         if (callbackfn == ScriptRuntime.UNDEFINED) {
  1204             typeError("not.a.function", "undefined");
  1207         if (!initialValuePresent) {
  1208             if (iter.hasNext()) {
  1209                 initialValue = iter.next();
  1210             } else {
  1211                 typeError("array.reduce.invalid.init");
  1215         //if initial value is ScriptRuntime.UNDEFINED - step forward once.
  1216         return new IteratorAction<Object>(Global.toObject(self), callbackfn, ScriptRuntime.UNDEFINED, initialValue, iter) {
  1217             @Override
  1218             protected boolean forEach(final Object val, final int i) throws Throwable {
  1219                 // TODO: why can't I declare the second arg as Undefined.class?
  1220                 result = REDUCE_CALLBACK_INVOKER.invokeExact(callbackfn, ScriptRuntime.UNDEFINED, result, val, i, self);
  1221                 return true;
  1223         }.apply();
  1226     /**
  1227      * ECMA 15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue ] )
  1229      * @param self self reference
  1230      * @param args arguments to reduce
  1231      * @return accumulated result
  1232      */
  1233     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1234     public static Object reduce(final Object self, final Object... args) {
  1235         return reduceInner(arrayLikeIterator(self), self, args);
  1238     /**
  1239      * ECMA 15.4.4.22 Array.prototype.reduceRight ( callbackfn [ , initialValue ] )
  1241      * @param self        self reference
  1242      * @param args arguments to reduce
  1243      * @return accumulated result
  1244      */
  1245     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
  1246     public static Object reduceRight(final Object self, final Object... args) {
  1247         return reduceInner(reverseArrayLikeIterator(self), self, args);
  1250     /**
  1251      * Determine if Java bulk array operations may be used on the underlying
  1252      * storage. This is possible only if the object's prototype chain is empty
  1253      * or each of the prototypes in the chain is empty.
  1255      * @param self the object to examine
  1256      * @return true if optimizable
  1257      */
  1258     private static boolean bulkable(final ScriptObject self) {
  1259         return self.isArray() && !hasInheritedArrayEntries(self);
  1262     private static boolean hasInheritedArrayEntries(final ScriptObject self) {
  1263         ScriptObject proto = self.getProto();
  1264         while (proto != null) {
  1265             if (proto.hasArrayEntries()) {
  1266                 return true;
  1268             proto = proto.getProto();
  1271         return false;
  1274     private static MethodHandle createIteratorCallbackInvoker(final Class<?> rtype) {
  1275         return Bootstrap.createDynamicInvoker("dyn:call", rtype, Object.class, Object.class, Object.class,
  1276                 int.class, Object.class);

mercurial