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