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