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