1 /* 2 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 30 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 31 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 32 import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; 33 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 35 import static jdk.nashorn.internal.runtime.JSType.isString; 36 37 import java.lang.invoke.MethodHandle; 38 import java.lang.invoke.MethodHandles; 39 import java.lang.invoke.SwitchPoint; 40 import java.lang.reflect.Array; 41 import java.util.Collections; 42 import java.util.Iterator; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.Map; 46 import java.util.NoSuchElementException; 47 import java.util.Objects; 48 import jdk.dynalink.beans.BeansLinker; 49 import jdk.dynalink.beans.StaticClass; 50 import jdk.nashorn.api.scripting.JSObject; 51 import jdk.nashorn.api.scripting.ScriptObjectMirror; 52 import jdk.nashorn.internal.codegen.ApplySpecialization; 53 import jdk.nashorn.internal.codegen.CompilerConstants; 54 import jdk.nashorn.internal.codegen.CompilerConstants.Call; 55 import jdk.nashorn.internal.ir.debug.JSONWriter; 56 import jdk.nashorn.internal.objects.AbstractIterator; 57 import jdk.nashorn.internal.objects.Global; 58 import jdk.nashorn.internal.objects.NativeObject; 59 import jdk.nashorn.internal.objects.NativeJava; 60 import jdk.nashorn.internal.objects.NativeArray; 61 import jdk.nashorn.internal.parser.Lexer; 62 import jdk.nashorn.internal.runtime.arrays.ArrayIndex; 63 import jdk.nashorn.internal.runtime.linker.Bootstrap; 64 import jdk.nashorn.internal.runtime.linker.InvokeByName; 65 66 /** 67 * Utilities to be called by JavaScript runtime API and generated classes. 68 */ 69 70 public final class ScriptRuntime { 71 private ScriptRuntime() { 72 } 73 74 /** Singleton representing the empty array object '[]' */ 75 public static final Object[] EMPTY_ARRAY = new Object[0]; 76 77 /** Unique instance of undefined. */ 78 public static final Undefined UNDEFINED = Undefined.getUndefined(); 79 80 /** 81 * Unique instance of undefined used to mark empty array slots. 82 * Can't escape the array. 83 */ 84 public static final Undefined EMPTY = Undefined.getEmpty(); 85 86 /** Method handle to generic + operator, operating on objects */ 87 public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class); 88 89 /** Method handle to generic === operator, operating on objects */ 90 public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class); 91 92 /** Method handle used to enter a {@code with} scope at runtime. */ 93 public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class); 94 95 /** 96 * Method used to place a scope's variable into the Global scope, which has to be done for the 97 * properties declared at outermost script level. 98 */ 99 public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class); 100 101 /** 102 * Return an appropriate iterator for the elements in a for-in construct 103 */ 104 public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class); 105 106 /** 107 * Return an appropriate iterator for the elements in a for-each construct 108 */ 109 public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class); 110 111 /** 112 * Return an appropriate iterator for the elements in a ES6 for-of loop 113 */ 114 public static final Call TO_ES6_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toES6Iterator", Iterator.class, Object.class); 115 116 /** 117 * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to 118 * call sites that are known to be megamorphic. Using an invoke dynamic here would 119 * lead to the JVM deoptimizing itself to death 120 */ 121 public static final Call APPLY = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class); 122 123 /** 124 * Throws a reference error for an undefined variable. 125 */ 126 public static final Call THROW_REFERENCE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwReferenceError", void.class, String.class); 127 128 /** 129 * Throws a reference error for an undefined variable. 130 */ 131 public static final Call THROW_CONST_TYPE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwConstTypeError", void.class, String.class); 132 133 /** 134 * Used to invalidate builtin names, e.g "Function" mapping to all properties in Function.prototype and Function.prototype itself. 135 */ 136 public static final Call INVALIDATE_RESERVED_BUILTIN_NAME = staticCallNoLookup(ScriptRuntime.class, "invalidateReservedBuiltinName", void.class, String.class); 137 138 /** 139 * Used to perform failed delete under strict mode 140 */ 141 public static final Call STRICT_FAIL_DELETE = staticCallNoLookup(ScriptRuntime.class, "strictFailDelete", boolean.class, String.class); 142 143 /** 144 * Used to find the scope for slow delete 145 */ 146 public static final Call SLOW_DELETE = staticCallNoLookup(ScriptRuntime.class, "slowDelete", boolean.class, ScriptObject.class, String.class); 147 148 /** 149 * Converts a switch tag value to a simple integer. deflt value if it can't. 150 * 151 * @param tag Switch statement tag value. 152 * @param deflt default to use if not convertible. 153 * @return int tag value (or deflt.) 154 */ 155 public static int switchTagAsInt(final Object tag, final int deflt) { 156 if (tag instanceof Number) { 157 final double d = ((Number)tag).doubleValue(); 158 if (isRepresentableAsInt(d)) { 159 return (int)d; 160 } 161 } 162 return deflt; 163 } 164 165 /** 166 * Converts a switch tag value to a simple integer. deflt value if it can't. 167 * 168 * @param tag Switch statement tag value. 169 * @param deflt default to use if not convertible. 170 * @return int tag value (or deflt.) 171 */ 172 public static int switchTagAsInt(final boolean tag, final int deflt) { 173 return deflt; 174 } 175 176 /** 177 * Converts a switch tag value to a simple integer. deflt value if it can't. 178 * 179 * @param tag Switch statement tag value. 180 * @param deflt default to use if not convertible. 181 * @return int tag value (or deflt.) 182 */ 183 public static int switchTagAsInt(final long tag, final int deflt) { 184 return isRepresentableAsInt(tag) ? (int)tag : deflt; 185 } 186 187 /** 188 * Converts a switch tag value to a simple integer. deflt value if it can't. 189 * 190 * @param tag Switch statement tag value. 191 * @param deflt default to use if not convertible. 192 * @return int tag value (or deflt.) 193 */ 194 public static int switchTagAsInt(final double tag, final int deflt) { 195 return isRepresentableAsInt(tag) ? (int)tag : deflt; 196 } 197 198 /** 199 * This is the builtin implementation of {@code Object.prototype.toString} 200 * @param self reference 201 * @return string representation as object 202 */ 203 public static String builtinObjectToString(final Object self) { 204 String className; 205 // Spec tells us to convert primitives by ToObject.. 206 // But we don't need to -- all we need is the right class name 207 // of the corresponding primitive wrapper type. 208 209 final JSType type = JSType.ofNoFunction(self); 210 211 switch (type) { 212 case BOOLEAN: 213 className = "Boolean"; 214 break; 215 case NUMBER: 216 className = "Number"; 217 break; 218 case STRING: 219 className = "String"; 220 break; 221 // special case of null and undefined 222 case NULL: 223 className = "Null"; 224 break; 225 case UNDEFINED: 226 className = "Undefined"; 227 break; 228 case OBJECT: 229 if (self instanceof ScriptObject) { 230 className = ((ScriptObject)self).getClassName(); 231 } else if (self instanceof JSObject) { 232 className = ((JSObject)self).getClassName(); 233 } else { 234 className = self.getClass().getName(); 235 } 236 break; 237 default: 238 // Nashorn extension: use Java class name 239 className = self.getClass().getName(); 240 break; 241 } 242 243 final StringBuilder sb = new StringBuilder(); 244 sb.append("[object "); 245 sb.append(className); 246 sb.append(']'); 247 248 return sb.toString(); 249 } 250 251 /** 252 * This is called whenever runtime wants to throw an error and wants to provide 253 * meaningful information about an object. We don't want to call toString which 254 * ends up calling "toString" from script world which may itself throw error. 255 * When we want to throw an error, we don't additional error from script land 256 * -- which may sometimes lead to infinite recursion. 257 * 258 * @param obj Object to converted to String safely (without calling user script) 259 * @return safe String representation of the given object 260 */ 261 public static String safeToString(final Object obj) { 262 return JSType.toStringImpl(obj, true); 263 } 264 265 /** 266 * Returns an iterator over property identifiers used in the {@code for...in} statement. Note that the ECMAScript 267 * 5.1 specification, chapter 12.6.4. uses the terminology "property names", which seems to imply that the property 268 * identifiers are expected to be strings, but this is not actually spelled out anywhere, and Nashorn will in some 269 * cases deviate from this. Namely, we guarantee to always return an iterator over {@link String} values for any 270 * built-in JavaScript object. We will however return an iterator over {@link Integer} objects for native Java 271 * arrays and {@link List} objects, as well as arbitrary objects representing keys of a {@link Map}. Therefore, the 272 * expression {@code typeof i} within a {@code for(i in obj)} statement can return something other than 273 * {@code string} when iterating over native Java arrays, {@code List}, and {@code Map} objects. 274 * @param obj object to iterate on. 275 * @return iterator over the object's property names. 276 */ 277 public static Iterator<?> toPropertyIterator(final Object obj) { 278 if (obj instanceof ScriptObject) { 279 return ((ScriptObject)obj).propertyIterator(); 280 } 281 282 if (obj != null && obj.getClass().isArray()) { 283 return new RangeIterator(Array.getLength(obj)); 284 } 285 286 if (obj instanceof JSObject) { 287 return ((JSObject)obj).keySet().iterator(); 288 } 289 290 if (obj instanceof List) { 291 return new RangeIterator(((List<?>)obj).size()); 292 } 293 294 if (obj instanceof Map) { 295 return ((Map<?,?>)obj).keySet().iterator(); 296 } 297 298 final Object wrapped = Global.instance().wrapAsObject(obj); 299 if (wrapped instanceof ScriptObject) { 300 return ((ScriptObject)wrapped).propertyIterator(); 301 } 302 303 return Collections.emptyIterator(); 304 } 305 306 private static final class RangeIterator implements Iterator<Integer> { 307 private final int length; 308 private int index; 309 310 RangeIterator(final int length) { 311 this.length = length; 312 } 313 314 @Override 315 public boolean hasNext() { 316 return index < length; 317 } 318 319 @Override 320 public Integer next() { 321 return index++; 322 } 323 324 @Override 325 public void remove() { 326 throw new UnsupportedOperationException("remove"); 327 } 328 } 329 330 // value Iterator for important Java objects - arrays, maps, iterables. 331 private static Iterator<?> iteratorForJavaArrayOrList(final Object obj) { 332 if (obj != null && obj.getClass().isArray()) { 333 final Object array = obj; 334 final int length = Array.getLength(obj); 335 336 return new Iterator<Object>() { 337 private int index = 0; 338 339 @Override 340 public boolean hasNext() { 341 return index < length; 342 } 343 344 @Override 345 public Object next() { 346 if (index >= length) { 347 throw new NoSuchElementException(); 348 } 349 return Array.get(array, index++); 350 } 351 352 @Override 353 public void remove() { 354 throw new UnsupportedOperationException("remove"); 355 } 356 }; 357 } 358 359 if (obj instanceof Iterable) { 360 return ((Iterable<?>)obj).iterator(); 361 } 362 363 return null; 364 } 365 366 /** 367 * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS 368 * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over 369 * map values. 370 * @param obj object to iterate on. 371 * @return iterator over the object's property values. 372 */ 373 public static Iterator<?> toValueIterator(final Object obj) { 374 if (obj instanceof ScriptObject) { 375 return ((ScriptObject)obj).valueIterator(); 376 } 377 378 if (obj instanceof JSObject) { 379 return ((JSObject)obj).values().iterator(); 380 } 381 382 final Iterator<?> itr = iteratorForJavaArrayOrList(obj); 383 if (itr != null) { 384 return itr; 385 } 386 387 if (obj instanceof Map) { 388 return ((Map<?,?>)obj).values().iterator(); 389 } 390 391 final Object wrapped = Global.instance().wrapAsObject(obj); 392 if (wrapped instanceof ScriptObject) { 393 return ((ScriptObject)wrapped).valueIterator(); 394 } 395 396 return Collections.emptyIterator(); 397 } 398 399 /** 400 * Returns an iterator over property values used in the {@code for ... of} statement. The iterator uses the 401 * Iterator interface defined in version 6 of the ECMAScript specification. 402 * 403 * @param obj object to iterate on. 404 * @return iterator based on the ECMA 6 Iterator interface. 405 */ 406 public static Iterator<?> toES6Iterator(final Object obj) { 407 // if not a ScriptObject, try convenience iterator for Java objects! 408 if (!(obj instanceof ScriptObject)) { 409 final Iterator<?> itr = iteratorForJavaArrayOrList(obj); 410 if (itr != null) { 411 return itr; 412 } 413 414 if (obj instanceof Map) { 415 return new Iterator<Object>() { 416 private Iterator<?> iter = ((Map<?,?>)obj).entrySet().iterator(); 417 418 @Override 419 public boolean hasNext() { 420 return iter.hasNext(); 421 } 422 423 @Override 424 public Object next() { 425 Map.Entry<?,?> next = (Map.Entry)iter.next(); 426 Object[] keyvalue = new Object[]{next.getKey(), next.getValue()}; 427 NativeArray array = NativeJava.from(null, keyvalue); 428 return array; 429 } 430 431 @Override 432 public void remove() { 433 iter.remove(); 434 } 435 }; 436 } 437 } 438 439 final Global global = Global.instance(); 440 final Object iterator = AbstractIterator.getIterator(Global.toObject(obj), global); 441 442 final InvokeByName nextInvoker = AbstractIterator.getNextInvoker(global); 443 final MethodHandle doneInvoker = AbstractIterator.getDoneInvoker(global); 444 final MethodHandle valueInvoker = AbstractIterator.getValueInvoker(global); 445 446 return new Iterator<Object>() { 447 448 private Object nextResult = nextResult(); 449 450 private Object nextResult() { 451 try { 452 final Object next = nextInvoker.getGetter().invokeExact(iterator); 453 if (Bootstrap.isCallable(next)) { 454 return nextInvoker.getInvoker().invokeExact(next, iterator, (Object) null); 455 } 456 } catch (final RuntimeException|Error r) { 457 throw r; 458 } catch (final Throwable t) { 459 throw new RuntimeException(t); 460 } 461 return null; 462 } 463 464 @Override 465 public boolean hasNext() { 466 if (nextResult == null) { 467 return false; 468 } 469 try { 470 final Object done = doneInvoker.invokeExact(nextResult); 471 return !JSType.toBoolean(done); 472 } catch (final RuntimeException|Error r) { 473 throw r; 474 } catch (final Throwable t) { 475 throw new RuntimeException(t); 476 } 477 } 478 479 @Override 480 public Object next() { 481 if (nextResult == null) { 482 return Undefined.getUndefined(); 483 } 484 try { 485 final Object result = nextResult; 486 nextResult = nextResult(); 487 return valueInvoker.invokeExact(result); 488 } catch (final RuntimeException|Error r) { 489 throw r; 490 } catch (final Throwable t) { 491 throw new RuntimeException(t); 492 } 493 } 494 495 @Override 496 public void remove() { 497 throw new UnsupportedOperationException("remove"); 498 } 499 }; 500 } 501 502 /** 503 * Merge a scope into its prototype's map. 504 * Merge a scope into its prototype. 505 * 506 * @param scope Scope to merge. 507 * @return prototype object after merge 508 */ 509 public static ScriptObject mergeScope(final ScriptObject scope) { 510 final ScriptObject parentScope = scope.getProto(); 511 parentScope.addBoundProperties(scope); 512 return parentScope; 513 } 514 515 /** 516 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 517 * better performance by creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)} 518 * then using its {@link MethodHandle#invokeExact(Object...)} method instead. 519 * 520 * @param target ScriptFunction object. 521 * @param self Receiver in call. 522 * @param args Call arguments. 523 * @return Call result. 524 */ 525 public static Object apply(final ScriptFunction target, final Object self, final Object... args) { 526 try { 527 return target.invoke(self, args); 528 } catch (final RuntimeException | Error e) { 529 throw e; 530 } catch (final Throwable t) { 531 throw new RuntimeException(t); 532 } 533 } 534 535 /** 536 * Throws a reference error for an undefined variable. 537 * 538 * @param name the variable name 539 */ 540 public static void throwReferenceError(final String name) { 541 throw referenceError("not.defined", name); 542 } 543 544 /** 545 * Throws a type error for an assignment to a const. 546 * 547 * @param name the const name 548 */ 549 public static void throwConstTypeError(final String name) { 550 throw typeError("assign.constant", name); 551 } 552 553 /** 554 * Call a script function as a constructor with given args. 555 * 556 * @param target ScriptFunction object. 557 * @param args Call arguments. 558 * @return Constructor call result. 559 */ 560 public static Object construct(final ScriptFunction target, final Object... args) { 561 try { 562 return target.construct(args); 563 } catch (final RuntimeException | Error e) { 564 throw e; 565 } catch (final Throwable t) { 566 throw new RuntimeException(t); 567 } 568 } 569 570 /** 571 * Generic implementation of ECMA 9.12 - SameValue algorithm 572 * 573 * @param x first value to compare 574 * @param y second value to compare 575 * 576 * @return true if both objects have the same value 577 */ 578 public static boolean sameValue(final Object x, final Object y) { 579 final JSType xType = JSType.ofNoFunction(x); 580 final JSType yType = JSType.ofNoFunction(y); 581 582 if (xType != yType) { 583 return false; 584 } 585 586 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 587 return true; 588 } 589 590 if (xType == JSType.NUMBER) { 591 final double xVal = ((Number)x).doubleValue(); 592 final double yVal = ((Number)y).doubleValue(); 593 594 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 595 return true; 596 } 597 598 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 599 if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) { 600 return false; 601 } 602 603 return xVal == yVal; 604 } 605 606 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 607 return x.equals(y); 608 } 609 610 return x == y; 611 } 612 613 /** 614 * Returns AST as JSON compatible string. This is used to 615 * implement "parse" function in resources/parse.js script. 616 * 617 * @param code code to be parsed 618 * @param name name of the code source (used for location) 619 * @param includeLoc tells whether to include location information for nodes or not 620 * @return JSON string representation of AST of the supplied code 621 */ 622 public static String parse(final String code, final String name, final boolean includeLoc) { 623 return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc); 624 } 625 626 /** 627 * Test whether a char is valid JavaScript whitespace 628 * @param ch a char 629 * @return true if valid JavaScript whitespace 630 */ 631 public static boolean isJSWhitespace(final char ch) { 632 return Lexer.isJSWhitespace(ch); 633 } 634 635 /** 636 * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement, 637 * use {@link ScriptObject#getProto()} on the scope. 638 * 639 * @param scope existing scope 640 * @param expression expression in with 641 * 642 * @return {@link WithObject} that is the new scope 643 */ 644 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 645 final Global global = Context.getGlobal(); 646 if (expression == UNDEFINED) { 647 throw typeError(global, "cant.apply.with.to.undefined"); 648 } else if (expression == null) { 649 throw typeError(global, "cant.apply.with.to.null"); 650 } 651 652 if (expression instanceof ScriptObjectMirror) { 653 final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); 654 if (unwrapped instanceof ScriptObject) { 655 return new WithObject(scope, (ScriptObject)unwrapped); 656 } 657 // foreign ScriptObjectMirror 658 final ScriptObject exprObj = global.newObject(); 659 NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); 660 return new WithObject(scope, exprObj); 661 } 662 663 final Object wrappedExpr = JSType.toScriptObject(global, expression); 664 if (wrappedExpr instanceof ScriptObject) { 665 return new WithObject(scope, (ScriptObject)wrappedExpr); 666 } 667 668 throw typeError(global, "cant.apply.with.to.non.scriptobject"); 669 } 670 671 /** 672 * ECMA 11.6.1 - The addition operator (+) - generic implementation 673 * 674 * @param x first term 675 * @param y second term 676 * 677 * @return result of addition 678 */ 679 public static Object ADD(final Object x, final Object y) { 680 // This prefix code to handle Number special is for optimization. 681 final boolean xIsNumber = x instanceof Number; 682 final boolean yIsNumber = y instanceof Number; 683 684 if (xIsNumber && yIsNumber) { 685 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 686 } 687 688 final boolean xIsUndefined = x == UNDEFINED; 689 final boolean yIsUndefined = y == UNDEFINED; 690 691 if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { 692 return Double.NaN; 693 } 694 695 // code below is as per the spec. 696 final Object xPrim = JSType.toPrimitive(x); 697 final Object yPrim = JSType.toPrimitive(y); 698 699 if (isString(xPrim) || isString(yPrim)) { 700 try { 701 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 702 } catch (final IllegalArgumentException iae) { 703 throw rangeError(iae, "concat.string.too.big"); 704 } 705 } 706 707 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 708 } 709 710 /** 711 * Debugger hook. 712 * TODO: currently unimplemented 713 * 714 * @return undefined 715 */ 716 public static Object DEBUGGER() { 717 return UNDEFINED; 718 } 719 720 /** 721 * New hook 722 * 723 * @param clazz type for the clss 724 * @param args constructor arguments 725 * 726 * @return undefined 727 */ 728 public static Object NEW(final Object clazz, final Object... args) { 729 return UNDEFINED; 730 } 731 732 /** 733 * ECMA 11.4.3 The typeof Operator - generic implementation 734 * 735 * @param object the object from which to retrieve property to type check 736 * @param property property in object to check 737 * 738 * @return type name 739 */ 740 public static Object TYPEOF(final Object object, final Object property) { 741 Object obj = object; 742 743 if (property != null) { 744 if (obj instanceof ScriptObject) { 745 // this is a scope identifier 746 assert property instanceof String; 747 final ScriptObject sobj = (ScriptObject) obj; 748 749 final FindProperty find = sobj.findProperty(property, true, true, sobj); 750 if (find != null) { 751 obj = find.getObjectValue(); 752 } else { 753 obj = sobj.invokeNoSuchProperty(property, false, UnwarrantedOptimismException.INVALID_PROGRAM_POINT); 754 } 755 756 if(Global.isLocationPropertyPlaceholder(obj)) { 757 if(CompilerConstants.__LINE__.name().equals(property)) { 758 obj = 0; 759 } else { 760 obj = ""; 761 } 762 } 763 } else if (object instanceof Undefined) { 764 obj = ((Undefined)obj).get(property); 765 } else if (object == null) { 766 throw typeError("cant.get.property", safeToString(property), "null"); 767 } else if (JSType.isPrimitive(obj)) { 768 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 769 } else if (obj instanceof JSObject) { 770 obj = ((JSObject)obj).getMember(property.toString()); 771 } else { 772 obj = UNDEFINED; 773 } 774 } 775 776 return JSType.of(obj).typeName(); 777 } 778 779 /** 780 * Throw ReferenceError when LHS of assignment or increment/decrement 781 * operator is not an assignable node (say a literal) 782 * 783 * @param lhs Evaluated LHS 784 * @param rhs Evaluated RHS 785 * @param msg Additional LHS info for error message 786 * @return undefined 787 */ 788 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 789 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 790 } 791 792 /** 793 * ECMA 11.4.1 - delete operator, implementation for slow scopes 794 * 795 * This implementation of 'delete' walks the scope chain to find the scope that contains the 796 * property to be deleted, then invokes delete on it. Always used on scopes, never strict. 797 * 798 * @param obj top scope object 799 * @param property property to delete 800 * 801 * @return true if property was successfully found and deleted 802 */ 803 public static boolean slowDelete(final ScriptObject obj, final String property) { 804 ScriptObject sobj = obj; 805 while (sobj != null && sobj.isScope()) { 806 final FindProperty find = sobj.findProperty(property, false); 807 if (find != null) { 808 return sobj.delete(property, false); 809 } 810 sobj = sobj.getProto(); 811 } 812 return obj.delete(property, false); 813 } 814 815 /** 816 * ECMA 11.4.1 - delete operator, special case 817 * 818 * This is 'delete' on a scope; it always fails under strict mode. 819 * It always throws an exception, but is declared to return a boolean 820 * to be compatible with the delete operator type. 821 * 822 * @param property property to delete 823 * @return nothing, always throws an exception. 824 */ 825 public static boolean strictFailDelete(final String property) { 826 throw syntaxError("strict.cant.delete", property); 827 } 828 829 /** 830 * ECMA 11.9.1 - The equals operator (==) - generic implementation 831 * 832 * @param x first object to compare 833 * @param y second object to compare 834 * 835 * @return true if type coerced versions of objects are equal 836 */ 837 public static boolean EQ(final Object x, final Object y) { 838 return equals(x, y); 839 } 840 841 /** 842 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 843 * 844 * @param x first object to compare 845 * @param y second object to compare 846 * 847 * @return true if type coerced versions of objects are not equal 848 */ 849 public static boolean NE(final Object x, final Object y) { 850 return !EQ(x, y); 851 } 852 853 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 854 private static boolean equals(final Object x, final Object y) { 855 // We want to keep this method small so we skip reference equality check for numbers 856 // as NaN should return false when compared to itself (JDK-8043608). 857 if (x == y && !(x instanceof Number)) { 858 return true; 859 } 860 if (x instanceof ScriptObject && y instanceof ScriptObject) { 861 return false; // x != y 862 } 863 if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) { 864 return ScriptObjectMirror.identical(x, y); 865 } 866 return equalValues(x, y); 867 } 868 869 /** 870 * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value 871 * comparison applies). 872 * @param x one value 873 * @param y another value 874 * @return true if they're equal according to 11.9.3 875 */ 876 private static boolean equalValues(final Object x, final Object y) { 877 final JSType xType = JSType.ofNoFunction(x); 878 final JSType yType = JSType.ofNoFunction(y); 879 880 if (xType == yType) { 881 return equalSameTypeValues(x, y, xType); 882 } 883 884 return equalDifferentTypeValues(x, y, xType, yType); 885 } 886 887 /** 888 * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares 889 * values belonging to the same JSType. 890 * @param x one value 891 * @param y another value 892 * @param type the common type for the values 893 * @return true if they're equal 894 */ 895 private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { 896 if (type == JSType.UNDEFINED || type == JSType.NULL) { 897 return true; 898 } 899 900 if (type == JSType.NUMBER) { 901 return ((Number)x).doubleValue() == ((Number)y).doubleValue(); 902 } 903 904 if (type == JSType.STRING) { 905 // String may be represented by ConsString 906 return x.toString().equals(y.toString()); 907 } 908 909 if (type == JSType.BOOLEAN) { 910 return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); 911 } 912 913 return x == y; 914 } 915 916 /** 917 * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. 918 * @param x one value 919 * @param y another value 920 * @param xType the type for the value x 921 * @param yType the type for the value y 922 * @return true if they're equal 923 */ 924 private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { 925 if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) { 926 return true; 927 } else if (isNumberAndString(xType, yType)) { 928 return equalNumberToString(x, y); 929 } else if (isNumberAndString(yType, xType)) { 930 // Can reverse order as both are primitives 931 return equalNumberToString(y, x); 932 } else if (xType == JSType.BOOLEAN) { 933 return equalBooleanToAny(x, y); 934 } else if (yType == JSType.BOOLEAN) { 935 // Can reverse order as y is primitive 936 return equalBooleanToAny(y, x); 937 } else if (isPrimitiveAndObject(xType, yType)) { 938 return equalWrappedPrimitiveToObject(x, y); 939 } else if (isPrimitiveAndObject(yType, xType)) { 940 // Can reverse order as y is primitive 941 return equalWrappedPrimitiveToObject(y, x); 942 } 943 944 return false; 945 } 946 947 private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) { 948 return xType == JSType.UNDEFINED && yType == JSType.NULL; 949 } 950 951 private static boolean isNumberAndString(final JSType xType, final JSType yType) { 952 return xType == JSType.NUMBER && yType == JSType.STRING; 953 } 954 955 private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) { 956 return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT; 957 } 958 959 private static boolean equalNumberToString(final Object num, final Object str) { 960 // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We 961 // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number 962 // comparison. 963 return ((Number)num).doubleValue() == JSType.toNumber(str.toString()); 964 } 965 966 private static boolean equalBooleanToAny(final Object bool, final Object any) { 967 return equals(JSType.toNumber((Boolean)bool), any); 968 } 969 970 private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) { 971 return equals(numOrStr, JSType.toPrimitive(any)); 972 } 973 974 /** 975 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 976 * 977 * @param x first object to compare 978 * @param y second object to compare 979 * 980 * @return true if objects are equal 981 */ 982 public static boolean EQ_STRICT(final Object x, final Object y) { 983 return strictEquals(x, y); 984 } 985 986 /** 987 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 988 * 989 * @param x first object to compare 990 * @param y second object to compare 991 * 992 * @return true if objects are not equal 993 */ 994 public static boolean NE_STRICT(final Object x, final Object y) { 995 return !EQ_STRICT(x, y); 996 } 997 998 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 999 private static boolean strictEquals(final Object x, final Object y) { 1000 // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having 1001 // NaN value is not equal to itself by value even though it is referentially. 1002 1003 final JSType xType = JSType.ofNoFunction(x); 1004 final JSType yType = JSType.ofNoFunction(y); 1005 1006 if (xType != yType) { 1007 return false; 1008 } 1009 1010 return equalSameTypeValues(x, y, xType); 1011 } 1012 1013 /** 1014 * ECMA 11.8.6 - The in operator - generic implementation 1015 * 1016 * @param property property to check for 1017 * @param obj object in which to check for property 1018 * 1019 * @return true if objects are equal 1020 */ 1021 public static boolean IN(final Object property, final Object obj) { 1022 final JSType rvalType = JSType.ofNoFunction(obj); 1023 1024 if (rvalType == JSType.OBJECT) { 1025 if (obj instanceof ScriptObject) { 1026 return ((ScriptObject)obj).has(property); 1027 } 1028 1029 if (obj instanceof JSObject) { 1030 return ((JSObject)obj).hasMember(Objects.toString(property)); 1031 } 1032 1033 final Object key = JSType.toPropertyKey(property); 1034 1035 if (obj instanceof StaticClass) { 1036 final Class<?> clazz = ((StaticClass) obj).getRepresentedClass(); 1037 return BeansLinker.getReadableStaticPropertyNames(clazz).contains(Objects.toString(key)) 1038 || BeansLinker.getStaticMethodNames(clazz).contains(Objects.toString(key)); 1039 } else { 1040 if (obj instanceof Map && ((Map) obj).containsKey(key)) { 1041 return true; 1042 } 1043 1044 final int index = ArrayIndex.getArrayIndex(key); 1045 if (index >= 0) { 1046 if (obj instanceof List && index < ((List) obj).size()) { 1047 return true; 1048 } 1049 if (obj.getClass().isArray() && index < Array.getLength(obj)) { 1050 return true; 1051 } 1052 } 1053 1054 return BeansLinker.getReadableInstancePropertyNames(obj.getClass()).contains(Objects.toString(key)) 1055 || BeansLinker.getInstanceMethodNames(obj.getClass()).contains(Objects.toString(key)); 1056 } 1057 } 1058 1059 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 1060 } 1061 1062 /** 1063 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 1064 * 1065 * @param obj first object to compare 1066 * @param clazz type to check against 1067 * 1068 * @return true if {@code obj} is an instanceof {@code clazz} 1069 */ 1070 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 1071 if (clazz instanceof ScriptFunction) { 1072 if (obj instanceof ScriptObject) { 1073 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 1074 } 1075 return false; 1076 } 1077 1078 if (clazz instanceof StaticClass) { 1079 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 1080 } 1081 1082 if (clazz instanceof JSObject) { 1083 return ((JSObject)clazz).isInstance(obj); 1084 } 1085 1086 // provide for reverse hook 1087 if (obj instanceof JSObject) { 1088 return ((JSObject)obj).isInstanceOf(clazz); 1089 } 1090 1091 throw typeError("instanceof.on.non.object"); 1092 } 1093 1094 /** 1095 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 1096 * 1097 * @param x first object to compare 1098 * @param y second object to compare 1099 * 1100 * @return true if x is less than y 1101 */ 1102 public static boolean LT(final Object x, final Object y) { 1103 final Object px = JSType.toPrimitive(x, Number.class); 1104 final Object py = JSType.toPrimitive(y, Number.class); 1105 1106 return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : 1107 JSType.toNumber(px) < JSType.toNumber(py); 1108 } 1109 1110 private static boolean areBothString(final Object x, final Object y) { 1111 return isString(x) && isString(y); 1112 } 1113 1114 /** 1115 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 1116 * 1117 * @param x first object to compare 1118 * @param y second object to compare 1119 * 1120 * @return true if x is greater than y 1121 */ 1122 public static boolean GT(final Object x, final Object y) { 1123 final Object px = JSType.toPrimitive(x, Number.class); 1124 final Object py = JSType.toPrimitive(y, Number.class); 1125 1126 return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : 1127 JSType.toNumber(px) > JSType.toNumber(py); 1128 } 1129 1130 /** 1131 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation 1132 * 1133 * @param x first object to compare 1134 * @param y second object to compare 1135 * 1136 * @return true if x is less than or equal to y 1137 */ 1138 public static boolean LE(final Object x, final Object y) { 1139 final Object px = JSType.toPrimitive(x, Number.class); 1140 final Object py = JSType.toPrimitive(y, Number.class); 1141 1142 return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : 1143 JSType.toNumber(px) <= JSType.toNumber(py); 1144 } 1145 1146 /** 1147 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 1148 * 1149 * @param x first object to compare 1150 * @param y second object to compare 1151 * 1152 * @return true if x is greater than or equal to y 1153 */ 1154 public static boolean GE(final Object x, final Object y) { 1155 final Object px = JSType.toPrimitive(x, Number.class); 1156 final Object py = JSType.toPrimitive(y, Number.class); 1157 1158 return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : 1159 JSType.toNumber(px) >= JSType.toNumber(py); 1160 } 1161 1162 /** 1163 * Tag a reserved name as invalidated - used when someone writes 1164 * to a property with this name - overly conservative, but link time 1165 * is too late to apply e.g. apply->call specialization 1166 * @param name property name 1167 */ 1168 public static void invalidateReservedBuiltinName(final String name) { 1169 final Context context = Context.getContextTrusted(); 1170 final SwitchPoint sp = context.getBuiltinSwitchPoint(name); 1171 assert sp != null; 1172 context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); 1173 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 1174 } 1175 1176 /** 1177 * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral). 1178 * 1179 * @param rawStrings array of template raw values 1180 * @param cookedStrings array of template values 1181 * @return template object 1182 */ 1183 public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) { 1184 final ScriptObject template = (ScriptObject)cookedStrings; 1185 final ScriptObject rawObj = (ScriptObject)rawStrings; 1186 assert rawObj.getArray().length() == template.getArray().length(); 1187 template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze()); 1188 template.freeze(); 1189 return template; 1190 } 1191 }