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