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