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