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