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