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