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 * Check that the target function is associated with current Context. 365 * And also make sure that 'self', if ScriptObject, is from current context. 366 * 367 * Call a function as a constructor given args. 368 * 369 * @param target ScriptFunction object. 370 * @param args Call arguments. 371 * @return Constructor call result. 372 */ 373 public static Object checkAndConstruct(final ScriptFunction target, final Object... args) { 374 final ScriptObject global = Context.getGlobalTrusted(); 375 if (! (global instanceof GlobalObject)) { 376 throw new IllegalStateException("No current global set"); 377 } 378 379 if (target.getContext() != global.getContext()) { 380 throw new IllegalArgumentException("'target' function is not from current Context"); 381 } 382 383 // all in order - call real 'construct' 384 return construct(target, args); 385 } 386 387 /* 388 * Call a script function as a constructor with given args. 389 * 390 * @param target ScriptFunction object. 391 * @param args Call arguments. 392 * @return Constructor call result. 393 */ 394 public static Object construct(final ScriptFunction target, final Object... args) { 395 try { 396 return target.construct(args); 397 } catch (final RuntimeException | Error e) { 398 throw e; 399 } catch (final Throwable t) { 400 throw new RuntimeException(t); 401 } 402 } 403 404 /** 405 * Generic implementation of ECMA 9.12 - SameValue algorithm 406 * 407 * @param x first value to compare 408 * @param y second value to compare 409 * 410 * @return true if both objects have the same value 411 */ 412 public static boolean sameValue(final Object x, final Object y) { 413 final JSType xType = JSType.of(x); 414 final JSType yType = JSType.of(y); 415 416 if (xType != yType) { 417 return false; 418 } 419 420 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 421 return true; 422 } 423 424 if (xType == JSType.NUMBER) { 425 final double xVal = ((Number)x).doubleValue(); 426 final double yVal = ((Number)y).doubleValue(); 427 428 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 429 return true; 430 } 431 432 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 433 if (xVal == 0.0 && (Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal))) { 434 return false; 435 } 436 437 return xVal == yVal; 438 } 439 440 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 441 return x.equals(y); 442 } 443 444 return (x == y); 445 } 446 447 /** 448 * Returns AST as JSON compatible string. This is used to 449 * implement "parse" function in resources/parse.js script. 450 * 451 * @param code code to be parsed 452 * @param name name of the code source (used for location) 453 * @param includeLoc tells whether to include location information for nodes or not 454 * @return JSON string representation of AST of the supplied code 455 */ 456 public static String parse(final String code, final String name, final boolean includeLoc) { 457 return JSONWriter.parse(Context.getContextTrusted().getEnv(), code, name, includeLoc); 458 } 459 460 /** 461 * Test whether a char is valid JavaScript whitespace 462 * @param ch a char 463 * @return true if valid JavaScript whitespace 464 */ 465 public static boolean isJSWhitespace(final char ch) { 466 return Lexer.isJSWhitespace(ch); 467 } 468 469 /** 470 * Entering a {@code with} node requires new scope. This is the implementation 471 * 472 * @param scope existing scope 473 * @param expression expression in with 474 * 475 * @return {@link WithObject} that is the new scope 476 */ 477 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 478 final ScriptObject global = Context.getGlobalTrusted(); 479 if (expression == UNDEFINED) { 480 throw typeError(global, "cant.apply.with.to.undefined"); 481 } else if (expression == null) { 482 throw typeError(global, "cant.apply.with.to.null"); 483 } 484 485 final ScriptObject withObject = new WithObject(scope, JSType.toScriptObject(global, expression)); 486 487 return withObject; 488 } 489 490 /** 491 * Exiting a {@code with} node requires restoring scope. This is the implementation 492 * 493 * @param scope existing scope 494 * 495 * @return restored scope 496 */ 497 public static ScriptObject closeWith(final ScriptObject scope) { 498 if (scope instanceof WithObject) { 499 return scope.getProto(); 500 } 501 return scope; 502 } 503 504 /** 505 * ECMA 11.6.1 - The addition operator (+) - generic implementation 506 * Compiler specializes using {@link jdk.nashorn.internal.codegen.RuntimeCallSite} 507 * if any type information is available for any of the operands 508 * 509 * @param x first term 510 * @param y second term 511 * 512 * @return result of addition 513 */ 514 public static Object ADD(final Object x, final Object y) { 515 // This prefix code to handle Number special is for optimization. 516 final boolean xIsNumber = x instanceof Number; 517 final boolean yIsNumber = y instanceof Number; 518 519 if (xIsNumber && yIsNumber) { 520 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 521 } 522 523 final boolean xIsUndefined = x == UNDEFINED; 524 final boolean yIsUndefined = y == UNDEFINED; 525 526 if ((xIsNumber && yIsUndefined) || (xIsUndefined && yIsNumber) || (xIsUndefined && yIsUndefined)) { 527 return Double.NaN; 528 } 529 530 // code below is as per the spec. 531 final Object xPrim = JSType.toPrimitive(x); 532 final Object yPrim = JSType.toPrimitive(y); 533 534 if (xPrim instanceof String || yPrim instanceof String 535 || xPrim instanceof ConsString || yPrim instanceof ConsString) { 536 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 537 } 538 539 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 540 } 541 542 /** 543 * Debugger hook. 544 * TODO: currently unimplemented 545 * 546 * @return undefined 547 */ 548 public static Object DEBUGGER() { 549 return UNDEFINED; 550 } 551 552 /** 553 * New hook 554 * 555 * @param clazz type for the clss 556 * @param args constructor arguments 557 * 558 * @return undefined 559 */ 560 public static Object NEW(final Object clazz, final Object... args) { 561 return UNDEFINED; 562 } 563 564 /** 565 * ECMA 11.4.3 The typeof Operator - generic implementation 566 * 567 * @param object the object from which to retrieve property to type check 568 * @param property property in object to check 569 * 570 * @return type name 571 */ 572 public static Object TYPEOF(final Object object, final Object property) { 573 Object obj = object; 574 575 if (property != null) { 576 if (obj instanceof ScriptObject) { 577 obj = ((ScriptObject)obj).get(property); 578 } else if (object instanceof Undefined) { 579 obj = ((Undefined)obj).get(property); 580 } else if (object == null) { 581 throw typeError("cant.get.property", safeToString(property), "null"); 582 } else if (JSType.isPrimitive(obj)) { 583 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 584 } else { 585 obj = UNDEFINED; 586 } 587 } 588 589 return JSType.of(obj).typeName(); 590 } 591 592 /** 593 * ECMA 11.4.2 - void operator 594 * 595 * @param object object to evaluate 596 * 597 * @return Undefined as the object type 598 */ 599 public static Object VOID(final Object object) { 600 if (object instanceof Number) { 601 if (Double.isNaN(((Number)object).doubleValue())) { 602 return Double.NaN; 603 } 604 } 605 606 return UNDEFINED; 607 } 608 609 /** 610 * Throw ReferenceError when LHS of assignment or increment/decrement 611 * operator is not an assignable node (say a literal) 612 * 613 * @param lhs Evaluated LHS 614 * @param rhs Evaluated RHS 615 * @param msg Additional LHS info for error message 616 * @return undefined 617 */ 618 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 619 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 620 } 621 622 /** 623 * ECMA 11.4.1 - delete operation, generic implementation 624 * 625 * @param obj object with property to delete 626 * @param property property to delete 627 * @param strict are we in strict mode 628 * 629 * @return true if property was successfully found and deleted 630 */ 631 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 632 if (obj instanceof ScriptObject) { 633 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 634 } 635 636 if (obj instanceof Undefined) { 637 return ((Undefined)obj).delete(property, false); 638 } 639 640 if (obj == null) { 641 throw typeError("cant.delete.property", safeToString(property), "null"); 642 } 643 644 if (obj instanceof ScriptObjectMirror) { 645 return ((ScriptObjectMirror)obj).delete(property); 646 } 647 648 if (JSType.isPrimitive(obj)) { 649 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 650 } 651 652 // if object is not reference type, vacuously delete is successful. 653 return true; 654 } 655 656 /** 657 * ECMA 11.4.1 - delete operator, special case 658 * 659 * This is 'delete' that always fails. We have to check strict mode and throw error. 660 * That is why this is a runtime function. Or else we could have inlined 'false'. 661 * 662 * @param property property to delete 663 * @param strict are we in strict mode 664 * 665 * @return false always 666 */ 667 public static boolean FAIL_DELETE(final Object property, final Object strict) { 668 if (Boolean.TRUE.equals(strict)) { 669 throw syntaxError("strict.cant.delete", safeToString(property)); 670 } 671 return false; 672 } 673 674 /** 675 * ECMA 11.9.1 - The equals operator (==) - generic implementation 676 * 677 * @param x first object to compare 678 * @param y second object to compare 679 * 680 * @return true if type coerced versions of objects are equal 681 */ 682 public static boolean EQ(final Object x, final Object y) { 683 return equals(x, y); 684 } 685 686 /** 687 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 688 * 689 * @param x first object to compare 690 * @param y second object to compare 691 * 692 * @return true if type coerced versions of objects are not equal 693 */ 694 public static boolean NE(final Object x, final Object y) { 695 return !EQ(x, y); 696 } 697 698 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 699 private static boolean equals(final Object x, final Object y) { 700 final JSType xType = JSType.of(x); 701 final JSType yType = JSType.of(y); 702 703 if (xType == yType) { 704 705 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 706 return true; 707 } 708 709 if (xType == JSType.NUMBER) { 710 final double xVal = ((Number)x).doubleValue(); 711 final double yVal = ((Number)y).doubleValue(); 712 if (Double.isNaN(xVal) || Double.isNaN(yVal)) { 713 return false; 714 } 715 716 return xVal == yVal; 717 } 718 719 if (xType == JSType.STRING) { 720 // String may be represented by ConsString 721 return x.toString().equals(y.toString()); 722 } 723 724 if (xType == JSType.BOOLEAN) { 725 // Boolean comparison 726 return x.equals(y); 727 } 728 729 return x == y; 730 } 731 732 if ((xType == JSType.UNDEFINED && yType == JSType.NULL) || 733 (xType == JSType.NULL && yType == JSType.UNDEFINED)) { 734 return true; 735 } 736 737 if (xType == JSType.NUMBER && yType == JSType.STRING) { 738 return EQ(x, JSType.toNumber(y)); 739 } 740 741 if (xType == JSType.STRING && yType == JSType.NUMBER) { 742 return EQ(JSType.toNumber(x), y); 743 } 744 745 if (xType == JSType.BOOLEAN) { 746 return EQ(JSType.toNumber(x), y); 747 } 748 749 if (yType == JSType.BOOLEAN) { 750 return EQ(x, JSType.toNumber(y)); 751 } 752 753 if ((xType == JSType.STRING || xType == JSType.NUMBER) && 754 (y instanceof ScriptObject)) { 755 return EQ(x, JSType.toPrimitive(y)); 756 } 757 758 if ((x instanceof ScriptObject) && 759 (yType == JSType.STRING || yType == JSType.NUMBER)) { 760 return EQ(JSType.toPrimitive(x), y); 761 } 762 763 return false; 764 } 765 766 /** 767 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 768 * 769 * @param x first object to compare 770 * @param y second object to compare 771 * 772 * @return true if objects are equal 773 */ 774 public static boolean EQ_STRICT(final Object x, final Object y) { 775 return strictEquals(x, y); 776 } 777 778 /** 779 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 780 * 781 * @param x first object to compare 782 * @param y second object to compare 783 * 784 * @return true if objects are not equal 785 */ 786 public static boolean NE_STRICT(final Object x, final Object y) { 787 return !EQ_STRICT(x, y); 788 } 789 790 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 791 private static boolean strictEquals(final Object x, final Object y) { 792 final JSType xType = JSType.of(x); 793 final JSType yType = JSType.of(y); 794 795 if (xType != yType) { 796 return false; 797 } 798 799 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 800 return true; 801 } 802 803 if (xType == JSType.NUMBER) { 804 final double xVal = ((Number)x).doubleValue(); 805 final double yVal = ((Number)y).doubleValue(); 806 807 if (Double.isNaN(xVal) || Double.isNaN(yVal)) { 808 return false; 809 } 810 811 return xVal == yVal; 812 } 813 814 if (xType == JSType.STRING) { 815 // String may be represented by ConsString 816 return x.toString().equals(y.toString()); 817 } 818 819 if (xType == JSType.BOOLEAN) { 820 return x.equals(y); 821 } 822 823 // finally, the object identity comparison 824 return x == y; 825 } 826 827 /** 828 * ECMA 11.8.6 - The in operator - generic implementation 829 * 830 * @param property property to check for 831 * @param obj object in which to check for property 832 * 833 * @return true if objects are equal 834 */ 835 public static boolean IN(final Object property, final Object obj) { 836 final JSType rvalType = JSType.of(obj); 837 838 if (rvalType == JSType.OBJECT || rvalType == JSType.FUNCTION) { 839 if (obj instanceof ScriptObject) { 840 return ((ScriptObject)obj).has(property); 841 } 842 843 return false; 844 } 845 846 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 847 } 848 849 /** 850 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 851 * 852 * @param obj first object to compare 853 * @param clazz type to check against 854 * 855 * @return true if {@code obj} is an instanceof {@code clazz} 856 */ 857 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 858 if (clazz instanceof ScriptFunction) { 859 if (obj instanceof ScriptObject) { 860 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 861 } 862 return false; 863 } 864 865 if (clazz instanceof StaticClass) { 866 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 867 } 868 869 if (clazz instanceof ScriptObjectMirror) { 870 if (obj instanceof ScriptObjectMirror) { 871 return ((ScriptObjectMirror)clazz).isInstance((ScriptObjectMirror)obj); 872 } 873 return false; 874 } 875 876 throw typeError("instanceof.on.non.object"); 877 } 878 879 /** 880 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 881 * 882 * @param x first object to compare 883 * @param y second object to compare 884 * 885 * @return true if x is less than y 886 */ 887 public static boolean LT(final Object x, final Object y) { 888 final Object value = lessThan(x, y, true); 889 return (value == UNDEFINED) ? false : (Boolean)value; 890 } 891 892 /** 893 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 894 * 895 * @param x first object to compare 896 * @param y second object to compare 897 * 898 * @return true if x is greater than y 899 */ 900 public static boolean GT(final Object x, final Object y) { 901 final Object value = lessThan(y, x, false); 902 return (value == UNDEFINED) ? false : (Boolean)value; 903 } 904 905 /** 906 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation 907 * 908 * @param x first object to compare 909 * @param y second object to compare 910 * 911 * @return true if x is less than or equal to y 912 */ 913 public static boolean LE(final Object x, final Object y) { 914 final Object value = lessThan(y, x, false); 915 return (!(Boolean.TRUE.equals(value) || value == UNDEFINED)); 916 } 917 918 /** 919 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 920 * 921 * @param x first object to compare 922 * @param y second object to compare 923 * 924 * @return true if x is greater than or equal to y 925 */ 926 public static boolean GE(final Object x, final Object y) { 927 final Object value = lessThan(x, y, true); 928 return (!(Boolean.TRUE.equals(value) || value == UNDEFINED)); 929 } 930 931 /** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */ 932 private static Object lessThan(final Object x, final Object y, final boolean leftFirst) { 933 Object px, py; 934 935 //support e.g. x < y should throw exception correctly if x or y are not numeric 936 if (leftFirst) { 937 px = JSType.toPrimitive(x, Number.class); 938 py = JSType.toPrimitive(y, Number.class); 939 } else { 940 py = JSType.toPrimitive(y, Number.class); 941 px = JSType.toPrimitive(x, Number.class); 942 } 943 944 if (JSType.of(px) == JSType.STRING && JSType.of(py) == JSType.STRING) { 945 // May be String or ConsString 946 return (px.toString()).compareTo(py.toString()) < 0; 947 } 948 949 final double nx = JSType.toNumber(px); 950 final double ny = JSType.toNumber(py); 951 952 if (Double.isNaN(nx) || Double.isNaN(ny)) { 953 return UNDEFINED; 954 } 955 956 if (nx == ny) { 957 return false; 958 } 959 960 if (nx > 0 && ny > 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { 961 return false; 962 } 963 964 if (nx < 0 && ny < 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { 965 return false; 966 } 967 968 return nx < ny; 969 } 970 971 } --- EOF ---