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