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  */
  25 
  26 package jdk.nashorn.internal.objects;
  27 
  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;
  34 
  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 java.util.concurrent.Callable;
  43 
  44 import jdk.nashorn.api.scripting.JSObject;
  45 import jdk.nashorn.internal.objects.annotations.Attribute;
  46 import jdk.nashorn.internal.objects.annotations.Constructor;
  47 import jdk.nashorn.internal.objects.annotations.Function;
  48 import jdk.nashorn.internal.objects.annotations.Getter;
  49 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  50 import jdk.nashorn.internal.objects.annotations.Setter;
  51 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
  52 import jdk.nashorn.internal.objects.annotations.Where;
  53 import jdk.nashorn.internal.runtime.JSType;
  54 import jdk.nashorn.internal.runtime.PropertyDescriptor;
  55 import jdk.nashorn.internal.runtime.PropertyMap;
  56 import jdk.nashorn.internal.runtime.ScriptFunction;
  57 import jdk.nashorn.internal.runtime.ScriptObject;
  58 import jdk.nashorn.internal.runtime.ScriptRuntime;
  59 import jdk.nashorn.internal.runtime.Undefined;
  60 import jdk.nashorn.internal.runtime.arrays.ArrayData;
  61 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
  62 import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
  63 import jdk.nashorn.internal.runtime.arrays.IteratorAction;
  64 import jdk.nashorn.internal.runtime.linker.Bootstrap;
  65 import jdk.nashorn.internal.runtime.linker.InvokeByName;
  66 
  67 /**
  68  * Runtime representation of a JavaScript array. NativeArray only holds numeric
  69  * keyed values. All other values are stored in spill.
  70  */
  71 @ScriptClass("Array")
  72 public final class NativeArray extends ScriptObject {
  73     private static final Object JOIN                     = new Object();
  74     private static final Object EVERY_CALLBACK_INVOKER   = new Object();
  75     private static final Object SOME_CALLBACK_INVOKER    = new Object();
  76     private static final Object FOREACH_CALLBACK_INVOKER = new Object();
  77     private static final Object MAP_CALLBACK_INVOKER     = new Object();
  78     private static final Object FILTER_CALLBACK_INVOKER  = new Object();
  79     private static final Object REDUCE_CALLBACK_INVOKER  = new Object();
  80     private static final Object CALL_CMP                 = new Object();
  81     private static final Object TO_LOCALE_STRING         = new Object();
  82 
  83     private static InvokeByName getJOIN() {
  84         return Global.instance().getInvokeByName(JOIN,
  85                 new Callable<InvokeByName>() {
  86                     @Override
  87                     public InvokeByName call() {
  88                         return new InvokeByName("join", ScriptObject.class);
  89                     }
  90                 });
  91     }
  92 
  93     private static MethodHandle createIteratorCallbackInvoker(final Object key, final Class<?> rtype) {
  94         return Global.instance().getDynamicInvoker(key,
  95             new Callable<MethodHandle>() {
  96                 @Override
  97                 public MethodHandle call() {
  98                     return Bootstrap.createDynamicInvoker("dyn:call", rtype, Object.class, Object.class, Object.class,
  99                         long.class, Object.class);
 100                 }
 101             });
 102     }
 103 
 104     private static MethodHandle getEVERY_CALLBACK_INVOKER() {
 105         return createIteratorCallbackInvoker(EVERY_CALLBACK_INVOKER, boolean.class);
 106     }
 107 
 108     private static MethodHandle getSOME_CALLBACK_INVOKER() {
 109         return createIteratorCallbackInvoker(SOME_CALLBACK_INVOKER, boolean.class);
 110     }
 111 
 112     private static MethodHandle getFOREACH_CALLBACK_INVOKER() {
 113         return createIteratorCallbackInvoker(FOREACH_CALLBACK_INVOKER, void.class);
 114     }
 115 
 116     private static MethodHandle getMAP_CALLBACK_INVOKER() {
 117         return createIteratorCallbackInvoker(MAP_CALLBACK_INVOKER, Object.class);
 118     }
 119 
 120     private static MethodHandle getFILTER_CALLBACK_INVOKER() {
 121         return createIteratorCallbackInvoker(FILTER_CALLBACK_INVOKER, boolean.class);
 122     }
 123 
 124     private static MethodHandle getREDUCE_CALLBACK_INVOKER() {
 125         return Global.instance().getDynamicInvoker(REDUCE_CALLBACK_INVOKER,
 126                 new Callable<MethodHandle>() {
 127                     @Override
 128                     public MethodHandle call() {
 129                         return Bootstrap.createDynamicInvoker("dyn:call", Object.class, Object.class,
 130                              Undefined.class, Object.class, Object.class, long.class, Object.class);
 131                     }
 132                 });
 133     }
 134 
 135     private static MethodHandle getCALL_CMP() {
 136         return Global.instance().getDynamicInvoker(CALL_CMP,
 137                 new Callable<MethodHandle>() {
 138                     @Override
 139                     public MethodHandle call() {
 140                         return Bootstrap.createDynamicInvoker("dyn:call", double.class,
 141                             ScriptFunction.class, Object.class, Object.class, Object.class);
 142                     }
 143                 });
 144     }
 145 
 146     private static InvokeByName getTO_LOCALE_STRING() {
 147         return Global.instance().getInvokeByName(TO_LOCALE_STRING,
 148                 new Callable<InvokeByName>() {
 149                     @Override
 150                     public InvokeByName call() {
 151                         return new InvokeByName("toLocaleString", ScriptObject.class, String.class);
 152                     }
 153                 });
 154     }
 155 
 156     // initialized by nasgen
 157     private static PropertyMap $nasgenmap$;
 158 
 159     /*
 160      * Constructors.
 161      */
 162     NativeArray() {
 163         this(ArrayData.initialArray());
 164     }
 165 
 166     NativeArray(final long length) {
 167         // TODO assert valid index in long before casting
 168         this(ArrayData.allocate((int) length));
 169     }
 170 
 171     NativeArray(final int[] array) {
 172         this(ArrayData.allocate(array));
 173     }
 174 
 175     NativeArray(final long[] array) {
 176         this(ArrayData.allocate(array));
 177     }
 178 
 179     NativeArray(final double[] array) {
 180         this(ArrayData.allocate(array));
 181     }
 182 
 183     NativeArray(final Object[] array) {
 184         this(ArrayData.allocate(array.length));
 185 
 186         ArrayData arrayData = this.getArray();
 187         arrayData.ensure(array.length - 1);
 188 
 189         for (int index = 0; index < array.length; index++) {
 190             final Object value = array[index];
 191 
 192             if (value == ScriptRuntime.EMPTY) {
 193                 arrayData = arrayData.delete(index);
 194             } else {
 195                 arrayData = arrayData.set(index, value, false);
 196             }
 197         }
 198 
 199         this.setArray(arrayData);
 200     }
 201 
 202     NativeArray(final ArrayData arrayData) {
 203         this(arrayData, Global.instance());
 204     }
 205 
 206     NativeArray(final ArrayData arrayData, final Global global) {
 207         super(global.getArrayPrototype(), $nasgenmap$);
 208         this.setArray(arrayData);
 209         this.setIsArray();
 210     }
 211 
 212     @Override
 213     public String getClassName() {
 214         return "Array";
 215     }
 216 
 217     @Override
 218     public Object getLength() {
 219         return getArray().length() & JSType.MAX_UINT;
 220     }
 221 
 222     /**
 223      * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw )
 224      */
 225     @Override
 226     public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) {
 227         final PropertyDescriptor desc = toPropertyDescriptor(Global.instance(), propertyDesc);
 228 
 229         // never be undefined as "length" is always defined and can't be deleted for arrays
 230         // Step 1
 231         final PropertyDescriptor oldLenDesc = (PropertyDescriptor) super.getOwnPropertyDescriptor("length");
 232 
 233         // Step 2
 234         // get old length and convert to long
 235         long oldLen = NativeArray.validLength(oldLenDesc.getValue(), true);
 236 
 237         // Step 3
 238         if ("length".equals(key)) {
 239             // check for length being made non-writable
 240             if (desc.has(WRITABLE) && !desc.isWritable()) {
 241                 setIsLengthNotWritable();
 242             }
 243 
 244             // Step 3a
 245             if (!desc.has(VALUE)) {
 246                 return super.defineOwnProperty("length", desc, reject);
 247             }
 248 
 249             // Step 3b
 250             final PropertyDescriptor newLenDesc = desc;
 251 
 252             // Step 3c and 3d - get new length and convert to long
 253             final long newLen = NativeArray.validLength(newLenDesc.getValue(), true);
 254 
 255             // Step 3e
 256             newLenDesc.setValue(newLen);
 257 
 258             // Step 3f
 259             // increasing array length - just need to set new length value (and attributes if any) and return
 260             if (newLen >= oldLen) {
 261                 return super.defineOwnProperty("length", newLenDesc, reject);
 262             }
 263 
 264             // Step 3g
 265             if (!oldLenDesc.isWritable()) {
 266                 if (reject) {
 267                     throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this));
 268                 }
 269                 return false;
 270             }
 271 
 272             // Step 3h and 3i
 273             final boolean newWritable = (!newLenDesc.has(WRITABLE) || newLenDesc.isWritable());
 274             if (!newWritable) {
 275                 newLenDesc.setWritable(true);
 276             }
 277 
 278             // Step 3j and 3k
 279             final boolean succeeded = super.defineOwnProperty("length", newLenDesc, reject);
 280             if (!succeeded) {
 281                 return false;
 282             }
 283 
 284             // Step 3l
 285             // make sure that length is set till the point we can delete the old elements
 286             while (newLen < oldLen) {
 287                 oldLen--;
 288                 final boolean deleteSucceeded = delete(oldLen, false);
 289                 if (!deleteSucceeded) {
 290                     newLenDesc.setValue(oldLen + 1);
 291                     if (!newWritable) {
 292                         newLenDesc.setWritable(false);
 293                     }
 294                     super.defineOwnProperty("length", newLenDesc, false);
 295                     if (reject) {
 296                         throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this));
 297                     }
 298                     return false;
 299                 }
 300             }
 301 
 302             // Step 3m
 303             if (!newWritable) {
 304                 // make 'length' property not writable
 305                 final ScriptObject newDesc = Global.newEmptyInstance();
 306                 newDesc.set(WRITABLE, false, false);
 307                 return super.defineOwnProperty("length", newDesc, false);
 308             }
 309 
 310             return true;
 311         }
 312 
 313         // Step 4a
 314         final int index = ArrayIndex.getArrayIndex(key);
 315         if (ArrayIndex.isValidArrayIndex(index)) {
 316             final long longIndex = ArrayIndex.toLongIndex(index);
 317             // Step 4b
 318             // setting an element beyond current length, but 'length' is not writable
 319             if (longIndex >= oldLen && !oldLenDesc.isWritable()) {
 320                 if (reject) {
 321                     throw typeError("property.not.writable", Long.toString(longIndex), ScriptRuntime.safeToString(this));
 322                 }
 323                 return false;
 324             }
 325 
 326             // Step 4c
 327             // set the new array element
 328             final boolean succeeded = super.defineOwnProperty(key, desc, false);
 329 
 330             // Step 4d
 331             if (!succeeded) {
 332                 if (reject) {
 333                     throw typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this));
 334                 }
 335                 return false;
 336             }
 337 
 338             // Step 4e -- adjust new length based on new element index that is set
 339             if (longIndex >= oldLen) {
 340                 oldLenDesc.setValue(longIndex + 1);
 341                 super.defineOwnProperty("length", oldLenDesc, false);
 342             }
 343 
 344             // Step 4f
 345             return true;
 346         }
 347 
 348         // not an index property
 349         return super.defineOwnProperty(key, desc, reject);
 350     }
 351 
 352     /**
 353      * Return the array contents upcasted as an ObjectArray, regardless of
 354      * representation
 355      *
 356      * @return an object array
 357      */
 358     public Object[] asObjectArray() {
 359         return getArray().asObjectArray();
 360     }
 361 
 362     /**
 363      * ECMA 15.4.3.2 Array.isArray ( arg )
 364      *
 365      * @param self self reference
 366      * @param arg  argument - object to check
 367      * @return true if argument is an array
 368      */
 369     @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
 370     public static Object isArray(final Object self, final Object arg) {
 371         return isArray(arg) || (arg instanceof JSObject && ((JSObject)arg).isArray());
 372     }
 373 
 374     /**
 375      * Length getter
 376      * @param self self reference
 377      * @return the length of the object
 378      */
 379     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
 380     public static Object length(final Object self) {
 381         if (isArray(self)) {
 382             return ((ScriptObject) self).getArray().length() & JSType.MAX_UINT;
 383         }
 384 
 385         return 0;
 386     }
 387 
 388     /**
 389      * Length setter
 390      * @param self   self reference
 391      * @param length new length property
 392      */
 393     @Setter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
 394     public static void length(final Object self, final Object length) {
 395         if (isArray(self)) {
 396             ((ScriptObject) self).setLength(validLength(length, true));
 397         }
 398     }
 399 
 400     /**
 401      * Prototype length getter
 402      * @param self self reference
 403      * @return the length of the object
 404      */
 405     @Getter(name = "length", where = Where.PROTOTYPE, attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
 406     public static Object getProtoLength(final Object self) {
 407         return length(self);  // Same as instance getter but we can't make nasgen use the same method for prototype
 408     }
 409 
 410     /**
 411      * Prototype length setter
 412      * @param self   self reference
 413      * @param length new length property
 414      */
 415     @Setter(name = "length", where = Where.PROTOTYPE, attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
 416     public static void setProtoLength(final Object self, final Object length) {
 417         length(self, length);  // Same as instance setter but we can't make nasgen use the same method for prototype
 418     }
 419 
 420     static long validLength(final Object length, final boolean reject) {
 421         final double doubleLength = JSType.toNumber(length);
 422         if (!Double.isNaN(doubleLength) && JSType.isRepresentableAsLong(doubleLength)) {
 423             final long len = (long) doubleLength;
 424             if (len >= 0 && len <= JSType.MAX_UINT) {
 425                 return len;
 426             }
 427         }
 428         if (reject) {
 429             throw rangeError("inappropriate.array.length", ScriptRuntime.safeToString(length));
 430         }
 431         return -1;
 432     }
 433 
 434     /**
 435      * ECMA 15.4.4.2 Array.prototype.toString ( )
 436      *
 437      * @param self self reference
 438      * @return string representation of array
 439      */
 440     @Function(attributes = Attribute.NOT_ENUMERABLE)
 441     public static Object toString(final Object self) {
 442         final Object obj = Global.toObject(self);
 443         if (obj instanceof ScriptObject) {
 444             final InvokeByName joinInvoker = getJOIN();
 445             final ScriptObject sobj = (ScriptObject)obj;
 446             try {
 447                 final Object join = joinInvoker.getGetter().invokeExact(sobj);
 448                 if (Bootstrap.isCallable(join)) {
 449                     return joinInvoker.getInvoker().invokeExact(join, sobj);
 450                 }
 451             } catch (final RuntimeException | Error e) {
 452                 throw e;
 453             } catch (final Throwable t) {
 454                 throw new RuntimeException(t);
 455             }
 456         }
 457 
 458         // FIXME: should lookup Object.prototype.toString and call that?
 459         return ScriptRuntime.builtinObjectToString(self);
 460     }
 461 
 462     /**
 463      * ECMA 15.4.4.3 Array.prototype.toLocaleString ( )
 464      *
 465      * @param self self reference
 466      * @return locale specific string representation for array
 467      */
 468     @Function(attributes = Attribute.NOT_ENUMERABLE)
 469     public static Object toLocaleString(final Object self) {
 470         final StringBuilder sb = new StringBuilder();
 471         final Iterator<Object> iter = arrayLikeIterator(self, true);
 472 
 473         while (iter.hasNext()) {
 474             final Object obj = iter.next();
 475 
 476             if (obj != null && obj != ScriptRuntime.UNDEFINED) {
 477                 final Object val = JSType.toScriptObject(obj);
 478 
 479                 try {
 480                     if (val instanceof ScriptObject) {
 481                         final InvokeByName localeInvoker = getTO_LOCALE_STRING();
 482                         final ScriptObject sobj           = (ScriptObject)val;
 483                         final Object       toLocaleString = localeInvoker.getGetter().invokeExact(sobj);
 484 
 485                         if (Bootstrap.isCallable(toLocaleString)) {
 486                             sb.append((String)localeInvoker.getInvoker().invokeExact(toLocaleString, sobj));
 487                         } else {
 488                             throw typeError("not.a.function", "toLocaleString");
 489                         }
 490                     }
 491                 } catch (final Error|RuntimeException t) {
 492                     throw t;
 493                 } catch (final Throwable t) {
 494                     throw new RuntimeException(t);
 495                 }
 496             }
 497 
 498             if (iter.hasNext()) {
 499                 sb.append(",");
 500             }
 501         }
 502 
 503         return sb.toString();
 504     }
 505 
 506     /**
 507      * ECMA 15.4.2.2 new Array (len)
 508      *
 509      * @param newObj was the new operator used to instantiate this array
 510      * @param self   self reference
 511      * @param args   arguments (length)
 512      * @return the new NativeArray
 513      */
 514     @Constructor(arity = 1)
 515     public static Object construct(final boolean newObj, final Object self, final Object... args) {
 516         switch (args.length) {
 517         case 0:
 518             return new NativeArray(0);
 519         case 1:
 520             final Object len = args[0];
 521             if (len instanceof Number) {
 522                 long length;
 523                 if (len instanceof Integer || len instanceof Long) {
 524                     length = ((Number) len).longValue();
 525                     if (length >= 0 && length < JSType.MAX_UINT) {
 526                         return new NativeArray(length);
 527                     }
 528                 }
 529 
 530                 length = JSType.toUint32(len);
 531 
 532                 /*
 533                  * If the argument len is a Number and ToUint32(len) is equal to
 534                  * len, then the length property of the newly constructed object
 535                  * is set to ToUint32(len). If the argument len is a Number and
 536                  * ToUint32(len) is not equal to len, a RangeError exception is
 537                  * thrown.
 538                  */
 539                 final double numberLength = ((Number) len).doubleValue();
 540                 if (length != numberLength) {
 541                     throw rangeError("inappropriate.array.length", JSType.toString(numberLength));
 542                 }
 543 
 544                 return new NativeArray(length);
 545             }
 546             /*
 547              * If the argument len is not a Number, then the length property of
 548              * the newly constructed object is set to 1 and the 0 property of
 549              * the newly constructed object is set to len
 550              */
 551             return new NativeArray(new Object[]{args[0]});
 552             //fallthru
 553         default:
 554             return new NativeArray(args);
 555         }
 556     }
 557 
 558     /**
 559      * ECMA 15.4.2.2 new Array (len)
 560      *
 561      * Specialized constructor for zero arguments - empty array
 562      *
 563      * @param newObj was the new operator used to instantiate this array
 564      * @param self   self reference
 565      * @return the new NativeArray
 566      */
 567     @SpecializedConstructor
 568     public static Object construct(final boolean newObj, final Object self) {
 569         return new NativeArray(0);
 570     }
 571 
 572     /**
 573      * ECMA 15.4.2.2 new Array (len)
 574      *
 575      * Specialized constructor for one integer argument (length)
 576      *
 577      * @param newObj was the new operator used to instantiate this array
 578      * @param self   self reference
 579      * @param length array length
 580      * @return the new NativeArray
 581      */
 582     @SpecializedConstructor
 583     public static Object construct(final boolean newObj, final Object self, final int length) {
 584         if (length >= 0) {
 585             return new NativeArray(length);
 586         }
 587 
 588         return construct(newObj, self, new Object[]{length});
 589     }
 590 
 591     /**
 592      * ECMA 15.4.2.2 new Array (len)
 593      *
 594      * Specialized constructor for one long argument (length)
 595      *
 596      * @param newObj was the new operator used to instantiate this array
 597      * @param self   self reference
 598      * @param length array length
 599      * @return the new NativeArray
 600      */
 601     @SpecializedConstructor
 602     public static Object construct(final boolean newObj, final Object self, final long length) {
 603         if (length >= 0L && length <= JSType.MAX_UINT) {
 604             return new NativeArray(length);
 605         }
 606 
 607         return construct(newObj, self, new Object[]{length});
 608     }
 609 
 610     /**
 611      * ECMA 15.4.2.2 new Array (len)
 612      *
 613      * Specialized constructor for one double argument (length)
 614      *
 615      * @param newObj was the new operator used to instantiate this array
 616      * @param self   self reference
 617      * @param length array length
 618      * @return the new NativeArray
 619      */
 620     @SpecializedConstructor
 621     public static Object construct(final boolean newObj, final Object self, final double length) {
 622         final long uint32length = JSType.toUint32(length);
 623 
 624         if (uint32length == length) {
 625             return new NativeArray(uint32length);
 626         }
 627 
 628         return construct(newObj, self, new Object[]{length});
 629     }
 630 
 631     /**
 632      * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
 633      *
 634      * @param self self reference
 635      * @param args arguments to concat
 636      * @return resulting NativeArray
 637      */
 638     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 639     public static Object concat(final Object self, final Object... args) {
 640         final ArrayList<Object> list = new ArrayList<>();
 641         concatToList(list, Global.toObject(self));
 642 
 643         for (final Object obj : args) {
 644             concatToList(list, obj);
 645         }
 646 
 647         return new NativeArray(list.toArray());
 648     }
 649 
 650     @SuppressWarnings("null")
 651     private static void concatToList(final ArrayList<Object> list, final Object obj) {
 652         final boolean isScriptArray = isArray(obj);
 653         final boolean isScriptObject = isScriptArray || obj instanceof ScriptObject;
 654         if (isScriptArray || obj instanceof Iterable || (obj != null && obj.getClass().isArray())) {
 655             final Iterator<Object> iter = arrayLikeIterator(obj, true);
 656             if (iter.hasNext()) {
 657                 for (int i = 0; iter.hasNext(); ++i) {
 658                     final Object value = iter.next();
 659                     if (value == ScriptRuntime.UNDEFINED && isScriptObject && !((ScriptObject)obj).has(i)) {
 660                         // TODO: eventually rewrite arrayLikeIterator to use a three-state enum for handling
 661                         // UNDEFINED instead of an "includeUndefined" boolean with states SKIP, INCLUDE,
 662                         // RETURN_EMPTY. Until then, this is how we'll make sure that empty elements don't make it
 663                         // into the concatenated array.
 664                         list.add(ScriptRuntime.EMPTY);
 665                     } else {
 666                         list.add(value);
 667                     }
 668                 }
 669             } else if (!isScriptArray) {
 670                 list.add(obj); // add empty object, but not an empty array
 671             }
 672         } else {
 673             // single element, add it
 674             list.add(obj);
 675         }
 676     }
 677 
 678     /**
 679      * ECMA 15.4.4.5 Array.prototype.join (separator)
 680      *
 681      * @param self      self reference
 682      * @param separator element separator
 683      * @return string representation after join
 684      */
 685     @Function(attributes = Attribute.NOT_ENUMERABLE)
 686     public static Object join(final Object self, final Object separator) {
 687         final StringBuilder    sb   = new StringBuilder();
 688         final Iterator<Object> iter = arrayLikeIterator(self, true);
 689         final String           sep  = separator == ScriptRuntime.UNDEFINED ? "," : JSType.toString(separator);
 690 
 691         while (iter.hasNext()) {
 692             final Object obj = iter.next();
 693 
 694             if (obj != null && obj != ScriptRuntime.UNDEFINED) {
 695                 sb.append(JSType.toString(obj));
 696             }
 697 
 698             if (iter.hasNext()) {
 699                 sb.append(sep);
 700             }
 701         }
 702 
 703         return sb.toString();
 704     }
 705 
 706     /**
 707      * ECMA 15.4.4.6 Array.prototype.pop ()
 708      *
 709      * @param self self reference
 710      * @return array after pop
 711      */
 712     @Function(attributes = Attribute.NOT_ENUMERABLE)
 713     public static Object pop(final Object self) {
 714         try {
 715             final ScriptObject sobj = (ScriptObject)self;
 716 
 717             if (bulkable(sobj)) {
 718                 return sobj.getArray().pop();
 719             }
 720 
 721             final long len = JSType.toUint32(sobj.getLength());
 722 
 723             if (len == 0) {
 724                 sobj.set("length", 0, true);
 725                 return ScriptRuntime.UNDEFINED;
 726             }
 727 
 728             final long   index   = len - 1;
 729             final Object element = sobj.get(index);
 730 
 731             sobj.delete(index, true);
 732             sobj.set("length", index, true);
 733 
 734             return element;
 735         } catch (final ClassCastException | NullPointerException e) {
 736             throw typeError("not.an.object", ScriptRuntime.safeToString(self));
 737         }
 738     }
 739 
 740     /**
 741      * ECMA 15.4.4.7 Array.prototype.push (args...)
 742      *
 743      * @param self self reference
 744      * @param args arguments to push
 745      * @return array after pushes
 746      */
 747     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 748     public static Object push(final Object self, final Object... args) {
 749         try {
 750             final ScriptObject sobj   = (ScriptObject)self;
 751 
 752             if (bulkable(sobj)) {
 753                 if (sobj.getArray().length() + args.length <= JSType.MAX_UINT) {
 754                     final ArrayData newData = sobj.getArray().push(true, args);
 755                     sobj.setArray(newData);
 756                     return newData.length();
 757                 }
 758                 //fallthru
 759             }
 760 
 761             long len = JSType.toUint32(sobj.getLength());
 762             for (final Object element : args) {
 763                 sobj.set(len++, element, true);
 764             }
 765             sobj.set("length", len, true);
 766 
 767             return len;
 768         } catch (final ClassCastException | NullPointerException e) {
 769             throw typeError("not.an.object", ScriptRuntime.safeToString(self));
 770         }
 771     }
 772 
 773     /**
 774      * ECMA 15.4.4.8 Array.prototype.reverse ()
 775      *
 776      * @param self self reference
 777      * @return reversed array
 778      */
 779     @Function(attributes = Attribute.NOT_ENUMERABLE)
 780     public static Object reverse(final Object self) {
 781         try {
 782             final ScriptObject sobj   = (ScriptObject)self;
 783             final long         len    = JSType.toUint32(sobj.getLength());
 784             final long         middle = len / 2;
 785 
 786             for (long lower = 0; lower != middle; lower++) {
 787                 final long    upper       = len - lower - 1;
 788                 final Object  lowerValue  = sobj.get(lower);
 789                 final Object  upperValue  = sobj.get(upper);
 790                 final boolean lowerExists = sobj.has(lower);
 791                 final boolean upperExists = sobj.has(upper);
 792 
 793                 if (lowerExists && upperExists) {
 794                     sobj.set(lower, upperValue, true);
 795                     sobj.set(upper, lowerValue, true);
 796                 } else if (!lowerExists && upperExists) {
 797                     sobj.set(lower, upperValue, true);
 798                     sobj.delete(upper, true);
 799                 } else if (lowerExists && !upperExists) {
 800                     sobj.delete(lower, true);
 801                     sobj.set(upper, lowerValue, true);
 802                 }
 803             }
 804             return sobj;
 805         } catch (final ClassCastException | NullPointerException e) {
 806             throw typeError("not.an.object", ScriptRuntime.safeToString(self));
 807         }
 808     }
 809 
 810     /**
 811      * ECMA 15.4.4.9 Array.prototype.shift ()
 812      *
 813      * @param self self reference
 814      * @return shifted array
 815      */
 816     @Function(attributes = Attribute.NOT_ENUMERABLE)
 817     public static Object shift(final Object self) {
 818         final Object obj = Global.toObject(self);
 819 
 820         Object first = ScriptRuntime.UNDEFINED;
 821 
 822         if (!(obj instanceof ScriptObject)) {
 823             return first;
 824         }
 825 
 826         final ScriptObject sobj   = (ScriptObject) obj;
 827 
 828         long len = JSType.toUint32(sobj.getLength());
 829 
 830         if (len > 0) {
 831             first = sobj.get(0);
 832 
 833             if (bulkable(sobj)) {
 834                 sobj.getArray().shiftLeft(1);
 835             } else {
 836                 boolean hasPrevious = true;
 837                 for (long k = 1; k < len; k++) {
 838                     boolean hasCurrent = sobj.has(k);
 839                     if (hasCurrent) {
 840                         sobj.set(k - 1, sobj.get(k), true);
 841                     } else if (hasPrevious) {
 842                         sobj.delete(k - 1, true);
 843                     }
 844                     hasPrevious = hasCurrent;
 845                 }
 846             }
 847             sobj.delete(--len, true);
 848         } else {
 849             len = 0;
 850         }
 851 
 852         sobj.set("length", len, true);
 853 
 854         return first;
 855     }
 856 
 857     /**
 858      * ECMA 15.4.4.10 Array.prototype.slice ( start [ , end ] )
 859      *
 860      * @param self  self reference
 861      * @param start start of slice (inclusive)
 862      * @param end   end of slice (optional, exclusive)
 863      * @return sliced array
 864      */
 865     @Function(attributes = Attribute.NOT_ENUMERABLE)
 866     public static Object slice(final Object self, final Object start, final Object end) {
 867         final Object       obj                 = Global.toObject(self);
 868         if (!(obj instanceof ScriptObject)) {
 869             return ScriptRuntime.UNDEFINED;
 870         }
 871 
 872         final ScriptObject sobj                = (ScriptObject)obj;
 873         final long         len                 = JSType.toUint32(sobj.getLength());
 874         final long         relativeStart       = JSType.toLong(start);
 875         final long         relativeEnd         = (end == ScriptRuntime.UNDEFINED) ? len : JSType.toLong(end);
 876 
 877         long k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
 878         final long finale = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
 879 
 880         if (k >= finale) {
 881             return new NativeArray(0);
 882         }
 883 
 884         if (bulkable(sobj)) {
 885             return new NativeArray(sobj.getArray().slice(k, finale));
 886         }
 887 
 888         // Construct array with proper length to have a deleted filter on undefined elements
 889         final NativeArray copy = new NativeArray(finale - k);
 890         for (long n = 0; k < finale; n++, k++) {
 891             if (sobj.has(k)) {
 892                 copy.defineOwnProperty(ArrayIndex.getArrayIndex(n), sobj.get(k));
 893             }
 894         }
 895 
 896         return copy;
 897     }
 898 
 899     private static ScriptFunction compareFunction(final Object comparefn) {
 900         if (comparefn == ScriptRuntime.UNDEFINED) {
 901             return null;
 902         }
 903 
 904         if (! (comparefn instanceof ScriptFunction)) {
 905             throw typeError("not.a.function", ScriptRuntime.safeToString(comparefn));
 906         }
 907 
 908         return (ScriptFunction)comparefn;
 909     }
 910 
 911     private static Object[] sort(final Object[] array, final Object comparefn) {
 912         final ScriptFunction cmp = compareFunction(comparefn);
 913 
 914         final List<Object> list = Arrays.asList(array);
 915         final Object cmpThis = cmp == null || cmp.isStrict() ? ScriptRuntime.UNDEFINED : Global.instance();
 916 
 917         Collections.sort(list, new Comparator<Object>() {
 918             private final MethodHandle call_cmp = getCALL_CMP();
 919             @Override
 920             public int compare(final Object x, final Object y) {
 921                 if (x == ScriptRuntime.UNDEFINED && y == ScriptRuntime.UNDEFINED) {
 922                     return 0;
 923                 } else if (x == ScriptRuntime.UNDEFINED) {
 924                     return 1;
 925                 } else if (y == ScriptRuntime.UNDEFINED) {
 926                     return -1;
 927                 }
 928 
 929                 if (cmp != null) {
 930                     try {
 931                         return (int)Math.signum((double)call_cmp.invokeExact(cmp, cmpThis, x, y));
 932                     } catch (final RuntimeException | Error e) {
 933                         throw e;
 934                     } catch (final Throwable t) {
 935                         throw new RuntimeException(t);
 936                     }
 937                 }
 938 
 939                 return JSType.toString(x).compareTo(JSType.toString(y));
 940             }
 941         });
 942 
 943         return list.toArray(new Object[array.length]);
 944     }
 945 
 946     /**
 947      * ECMA 15.4.4.11 Array.prototype.sort ( comparefn )
 948      *
 949      * @param self       self reference
 950      * @param comparefn  element comparison function
 951      * @return sorted array
 952      */
 953     @Function(attributes = Attribute.NOT_ENUMERABLE)
 954     public static Object sort(final Object self, final Object comparefn) {
 955         try {
 956             final ScriptObject sobj    = (ScriptObject) self;
 957             final long         len     = JSType.toUint32(sobj.getLength());
 958             ArrayData          array   = sobj.getArray();
 959 
 960             if (len > 1) {
 961                 // Get only non-missing elements. Missing elements go at the end
 962                 // of the sorted array. So, just don't copy these to sort input.
 963                 final ArrayList<Object> src = new ArrayList<>();
 964                 for (long i = 0; i < len; i = array.nextIndex(i)) {
 965                     if (array.has((int) i)) {
 966                         src.add(array.getObject((int) i));
 967                     }
 968                 }
 969 
 970                 final Object[] sorted = sort(src.toArray(), comparefn);
 971 
 972                 for (int i = 0; i < sorted.length; i++) {
 973                     array = array.set(i, sorted[i], true);
 974                 }
 975 
 976                 // delete missing elements - which are at the end of sorted array
 977                 if (sorted.length != len) {
 978                     array = array.delete(sorted.length, len - 1);
 979                 }
 980 
 981                 sobj.setArray(array);
 982            }
 983 
 984             return sobj;
 985         } catch (final ClassCastException | NullPointerException e) {
 986             throw typeError("not.an.object", ScriptRuntime.safeToString(self));
 987         }
 988     }
 989 
 990     /**
 991      * ECMA 15.4.4.12 Array.prototype.splice ( start, deleteCount [ item1 [ , item2 [ , ... ] ] ] )
 992      *
 993      * @param self self reference
 994      * @param args arguments
 995      * @return result of splice
 996      */
 997     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
 998     public static Object splice(final Object self, final Object... args) {
 999         final Object obj = Global.toObject(self);
1000 
1001         if (!(obj instanceof ScriptObject)) {
1002             return ScriptRuntime.UNDEFINED;
1003         }
1004 
1005         final Object start = (args.length > 0) ? args[0] : ScriptRuntime.UNDEFINED;
1006         final Object deleteCount = (args.length > 1) ? args[1] : ScriptRuntime.UNDEFINED;
1007 
1008         Object[] items;
1009 
1010         if (args.length > 2) {
1011             items = new Object[args.length - 2];
1012             System.arraycopy(args, 2, items, 0, items.length);
1013         } else {
1014             items = ScriptRuntime.EMPTY_ARRAY;
1015         }
1016 
1017         final ScriptObject sobj                = (ScriptObject)obj;
1018         final long         len                 = JSType.toUint32(sobj.getLength());
1019         final long         relativeStart       = JSType.toLong(start);
1020 
1021         final long actualStart = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
1022         final long actualDeleteCount = Math.min(Math.max(JSType.toLong(deleteCount), 0), len - actualStart);
1023 
1024         NativeArray returnValue;
1025 
1026         if (actualStart <= Integer.MAX_VALUE && actualDeleteCount <= Integer.MAX_VALUE && bulkable(sobj)) {
1027             try {
1028                 returnValue =  new NativeArray(sobj.getArray().fastSplice((int)actualStart, (int)actualDeleteCount, items.length));
1029 
1030                 // Since this is a dense bulkable array we can use faster defineOwnProperty to copy new elements
1031                 int k = (int) actualStart;
1032                 for (int i = 0; i < items.length; i++, k++) {
1033                     sobj.defineOwnProperty(k, items[i]);
1034                 }
1035             } catch (UnsupportedOperationException uoe) {
1036                 returnValue = slowSplice(sobj, actualStart, actualDeleteCount, items, len);
1037             }
1038         } else {
1039             returnValue = slowSplice(sobj, actualStart, actualDeleteCount, items, len);
1040         }
1041 
1042         return returnValue;
1043     }
1044 
1045     private static NativeArray slowSplice(final ScriptObject sobj, final long start, final long deleteCount, final Object[] items, final long len) {
1046 
1047         final NativeArray array = new NativeArray(deleteCount);
1048 
1049         for (long k = 0; k < deleteCount; k++) {
1050             final long from = start + k;
1051 
1052             if (sobj.has(from)) {
1053                 array.defineOwnProperty(ArrayIndex.getArrayIndex(k), sobj.get(from));
1054             }
1055         }
1056 
1057         if (items.length < deleteCount) {
1058             for (long k = start; k < (len - deleteCount); k++) {
1059                 final long from = k + deleteCount;
1060                 final long to   = k + items.length;
1061 
1062                 if (sobj.has(from)) {
1063                     sobj.set(to, sobj.get(from), true);
1064                 } else {
1065                     sobj.delete(to, true);
1066                 }
1067             }
1068 
1069             for (long k = len; k > (len - deleteCount + items.length); k--) {
1070                 sobj.delete(k - 1, true);
1071             }
1072         } else if (items.length > deleteCount) {
1073             for (long k = len - deleteCount; k > start; k--) {
1074                 final long from = k + deleteCount - 1;
1075                 final long to   = k + items.length - 1;
1076 
1077                 if (sobj.has(from)) {
1078                     final Object fromValue = sobj.get(from);
1079                     sobj.set(to, fromValue, true);
1080                 } else {
1081                     sobj.delete(to, true);
1082                 }
1083             }
1084         }
1085 
1086         long k = start;
1087         for (int i = 0; i < items.length; i++, k++) {
1088             sobj.set(k, items[i], true);
1089         }
1090 
1091         final long newLength = len - deleteCount + items.length;
1092         sobj.set("length", newLength, true);
1093 
1094         return array;
1095     }
1096 
1097     /**
1098      * ECMA 15.4.4.13 Array.prototype.unshift ( [ item1 [ , item2 [ , ... ] ] ] )
1099      *
1100      * @param self  self reference
1101      * @param items items for unshift
1102      * @return unshifted array
1103      */
1104     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1105     public static Object unshift(final Object self, final Object... items) {
1106         final Object obj = Global.toObject(self);
1107 
1108         if (!(obj instanceof ScriptObject)) {
1109             return ScriptRuntime.UNDEFINED;
1110         }
1111 
1112         final ScriptObject sobj   = (ScriptObject)obj;
1113         final long         len    = JSType.toUint32(sobj.getLength());
1114 
1115         if (items == null) {
1116             return ScriptRuntime.UNDEFINED;
1117         }
1118 
1119         if (bulkable(sobj)) {
1120             sobj.getArray().shiftRight(items.length);
1121 
1122             for (int j = 0; j < items.length; j++) {
1123                 sobj.setArray(sobj.getArray().set(j, items[j], true));
1124             }
1125         } else {
1126             for (long k = len; k > 0; k--) {
1127                 final long from = k - 1;
1128                 final long to = k + items.length - 1;
1129 
1130                 if (sobj.has(from)) {
1131                     final Object fromValue = sobj.get(from);
1132                     sobj.set(to, fromValue, true);
1133                 } else {
1134                     sobj.delete(to, true);
1135                 }
1136             }
1137 
1138             for (int j = 0; j < items.length; j++) {
1139                  sobj.set(j, items[j], true);
1140             }
1141         }
1142 
1143         final long newLength = len + items.length;
1144         sobj.set("length", newLength, true);
1145 
1146         return newLength;
1147     }
1148 
1149     /**
1150      * ECMA 15.4.4.14 Array.prototype.indexOf ( searchElement [ , fromIndex ] )
1151      *
1152      * @param self           self reference
1153      * @param searchElement  element to search for
1154      * @param fromIndex      start index of search
1155      * @return index of element, or -1 if not found
1156      */
1157     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1158     public static Object indexOf(final Object self, final Object searchElement, final Object fromIndex) {
1159         try {
1160             final ScriptObject sobj = (ScriptObject)Global.toObject(self);
1161             final long         len  = JSType.toUint32(sobj.getLength());
1162             if (len == 0) {
1163                 return -1;
1164             }
1165 
1166             final long         n = JSType.toLong(fromIndex);
1167             if (n >= len) {
1168                 return -1;
1169             }
1170 
1171 
1172             for (long k = Math.max(0, (n < 0) ? (len - Math.abs(n)) : n); k < len; k++) {
1173                 if (sobj.has(k)) {
1174                     if (ScriptRuntime.EQ_STRICT(sobj.get(k), searchElement)) {
1175                         return k;
1176                     }
1177                 }
1178             }
1179         } catch (final ClassCastException | NullPointerException e) {
1180             //fallthru
1181         }
1182 
1183         return -1;
1184     }
1185 
1186     /**
1187      * ECMA 15.4.4.15 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )
1188      *
1189      * @param self self reference
1190      * @param args arguments: element to search for and optional from index
1191      * @return index of element, or -1 if not found
1192      */
1193     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1194     public static Object lastIndexOf(final Object self, final Object... args) {
1195         try {
1196             final ScriptObject sobj = (ScriptObject)Global.toObject(self);
1197             final long         len  = JSType.toUint32(sobj.getLength());
1198 
1199             if (len == 0) {
1200                 return -1;
1201             }
1202 
1203             final Object searchElement = (args.length > 0) ? args[0] : ScriptRuntime.UNDEFINED;
1204             final long   n             = (args.length > 1) ? JSType.toLong(args[1]) : (len - 1);
1205 
1206             for (long k = (n < 0) ? (len - Math.abs(n)) : Math.min(n, len - 1); k >= 0; k--) {
1207                 if (sobj.has(k)) {
1208                     if (ScriptRuntime.EQ_STRICT(sobj.get(k), searchElement)) {
1209                         return k;
1210                     }
1211                 }
1212             }
1213         } catch (final ClassCastException | NullPointerException e) {
1214             throw typeError("not.an.object", ScriptRuntime.safeToString(self));
1215         }
1216 
1217         return -1;
1218     }
1219 
1220     /**
1221      * ECMA 15.4.4.16 Array.prototype.every ( callbackfn [ , thisArg ] )
1222      *
1223      * @param self        self reference
1224      * @param callbackfn  callback function per element
1225      * @param thisArg     this argument
1226      * @return true if callback function return true for every element in the array, false otherwise
1227      */
1228     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1229     public static Object every(final Object self, final Object callbackfn, final Object thisArg) {
1230         return applyEvery(Global.toObject(self), callbackfn, thisArg);
1231     }
1232 
1233     private static boolean applyEvery(final Object self, final Object callbackfn, final Object thisArg) {
1234         return new IteratorAction<Boolean>(Global.toObject(self), callbackfn, thisArg, true) {
1235             private final MethodHandle everyInvoker = getEVERY_CALLBACK_INVOKER();
1236 
1237             @Override
1238             protected boolean forEach(final Object val, final long i) throws Throwable {
1239                 return (result = (boolean)everyInvoker.invokeExact(callbackfn, thisArg, val, i, self));
1240             }
1241         }.apply();
1242     }
1243 
1244     /**
1245      * ECMA 15.4.4.17 Array.prototype.some ( callbackfn [ , thisArg ] )
1246      *
1247      * @param self        self reference
1248      * @param callbackfn  callback function per element
1249      * @param thisArg     this argument
1250      * @return true if callback function returned true for any element in the array, false otherwise
1251      */
1252     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1253     public static Object some(final Object self, final Object callbackfn, final Object thisArg) {
1254         return new IteratorAction<Boolean>(Global.toObject(self), callbackfn, thisArg, false) {
1255             private final MethodHandle someInvoker = getSOME_CALLBACK_INVOKER();
1256 
1257             @Override
1258             protected boolean forEach(final Object val, final long i) throws Throwable {
1259                 return !(result = (boolean)someInvoker.invokeExact(callbackfn, thisArg, val, i, self));
1260             }
1261         }.apply();
1262     }
1263 
1264     /**
1265      * ECMA 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
1266      *
1267      * @param self        self reference
1268      * @param callbackfn  callback function per element
1269      * @param thisArg     this argument
1270      * @return undefined
1271      */
1272     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1273     public static Object forEach(final Object self, final Object callbackfn, final Object thisArg) {
1274         return new IteratorAction<Object>(Global.toObject(self), callbackfn, thisArg, ScriptRuntime.UNDEFINED) {
1275             private final MethodHandle forEachInvoker = getFOREACH_CALLBACK_INVOKER();
1276 
1277             @Override
1278             protected boolean forEach(final Object val, final long i) throws Throwable {
1279                 forEachInvoker.invokeExact(callbackfn, thisArg, val, i, self);
1280                 return true;
1281             }
1282         }.apply();
1283     }
1284 
1285     /**
1286      * ECMA 15.4.4.19 Array.prototype.map ( callbackfn [ , thisArg ] )
1287      *
1288      * @param self        self reference
1289      * @param callbackfn  callback function per element
1290      * @param thisArg     this argument
1291      * @return array with elements transformed by map function
1292      */
1293     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1294     public static Object map(final Object self, final Object callbackfn, final Object thisArg) {
1295         return new IteratorAction<NativeArray>(Global.toObject(self), callbackfn, thisArg, null) {
1296             private final MethodHandle mapInvoker = getMAP_CALLBACK_INVOKER();
1297 
1298             @Override
1299             protected boolean forEach(final Object val, final long i) throws Throwable {
1300                 final Object r = mapInvoker.invokeExact(callbackfn, thisArg, val, i, self);
1301                 result.defineOwnProperty(ArrayIndex.getArrayIndex(index), r);
1302                 return true;
1303             }
1304 
1305             @Override
1306             public void applyLoopBegin(final ArrayLikeIterator<Object> iter0) {
1307                 // map return array should be of same length as source array
1308                 // even if callback reduces source array length
1309                 result = new NativeArray(iter0.getLength());
1310             }
1311         }.apply();
1312     }
1313 
1314     /**
1315      * ECMA 15.4.4.20 Array.prototype.filter ( callbackfn [ , thisArg ] )
1316      *
1317      * @param self        self reference
1318      * @param callbackfn  callback function per element
1319      * @param thisArg     this argument
1320      * @return filtered array
1321      */
1322     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1323     public static Object filter(final Object self, final Object callbackfn, final Object thisArg) {
1324         return new IteratorAction<NativeArray>(Global.toObject(self), callbackfn, thisArg, new NativeArray()) {
1325             private long to = 0;
1326             private final MethodHandle filterInvoker = getFILTER_CALLBACK_INVOKER();
1327 
1328             @Override
1329             protected boolean forEach(final Object val, final long i) throws Throwable {
1330                 if ((boolean)filterInvoker.invokeExact(callbackfn, thisArg, val, i, self)) {
1331                     result.defineOwnProperty(ArrayIndex.getArrayIndex(to++), val);
1332                 }
1333                 return true;
1334             }
1335         }.apply();
1336     }
1337 
1338     private static Object reduceInner(final ArrayLikeIterator<Object> iter, final Object self, final Object... args) {
1339         final Object  callbackfn          = args.length > 0 ? args[0] : ScriptRuntime.UNDEFINED;
1340         final boolean initialValuePresent = args.length > 1;
1341 
1342         Object initialValue = initialValuePresent ? args[1] : ScriptRuntime.UNDEFINED;
1343 
1344         if (callbackfn == ScriptRuntime.UNDEFINED) {
1345             throw typeError("not.a.function", "undefined");
1346         }
1347 
1348         if (!initialValuePresent) {
1349             if (iter.hasNext()) {
1350                 initialValue = iter.next();
1351             } else {
1352                 throw typeError("array.reduce.invalid.init");
1353             }
1354         }
1355 
1356         //if initial value is ScriptRuntime.UNDEFINED - step forward once.
1357         return new IteratorAction<Object>(Global.toObject(self), callbackfn, ScriptRuntime.UNDEFINED, initialValue, iter) {
1358             private final MethodHandle reduceInvoker = getREDUCE_CALLBACK_INVOKER();
1359 
1360             @Override
1361             protected boolean forEach(final Object val, final long i) throws Throwable {
1362                 // TODO: why can't I declare the second arg as Undefined.class?
1363                 result = reduceInvoker.invokeExact(callbackfn, ScriptRuntime.UNDEFINED, result, val, i, self);
1364                 return true;
1365             }
1366         }.apply();
1367     }
1368 
1369     /**
1370      * ECMA 15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue ] )
1371      *
1372      * @param self self reference
1373      * @param args arguments to reduce
1374      * @return accumulated result
1375      */
1376     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1377     public static Object reduce(final Object self, final Object... args) {
1378         return reduceInner(arrayLikeIterator(self), self, args);
1379     }
1380 
1381     /**
1382      * ECMA 15.4.4.22 Array.prototype.reduceRight ( callbackfn [ , initialValue ] )
1383      *
1384      * @param self        self reference
1385      * @param args arguments to reduce
1386      * @return accumulated result
1387      */
1388     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1389     public static Object reduceRight(final Object self, final Object... args) {
1390         return reduceInner(reverseArrayLikeIterator(self), self, args);
1391     }
1392 
1393     /**
1394      * Determine if Java bulk array operations may be used on the underlying
1395      * storage. This is possible only if the object's prototype chain is empty
1396      * or each of the prototypes in the chain is empty.
1397      *
1398      * @param self the object to examine
1399      * @return true if optimizable
1400      */
1401     private static boolean bulkable(final ScriptObject self) {
1402         return self.isArray() && !hasInheritedArrayEntries(self) && !self.isLengthNotWritable();
1403     }
1404 
1405     private static boolean hasInheritedArrayEntries(final ScriptObject self) {
1406         ScriptObject proto = self.getProto();
1407         while (proto != null) {
1408             if (proto.hasArrayEntries()) {
1409                 return true;
1410             }
1411             proto = proto.getProto();
1412         }
1413 
1414         return false;
1415     }
1416 }