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