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