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