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