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