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