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