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