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