ScriptRuntime.java revision 953:221a84ef44c0
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 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; 29import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 30import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 31import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 32import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; 33import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 35 36import java.lang.invoke.MethodHandle; 37import java.lang.invoke.MethodHandles; 38import java.lang.reflect.Array; 39import java.util.Collections; 40import java.util.Iterator; 41import java.util.List; 42import java.util.Locale; 43import java.util.Map; 44import java.util.NoSuchElementException; 45import java.util.Objects; 46import jdk.internal.dynalink.beans.StaticClass; 47import jdk.nashorn.api.scripting.JSObject; 48import jdk.nashorn.api.scripting.ScriptObjectMirror; 49import jdk.nashorn.internal.codegen.CompilerConstants; 50import jdk.nashorn.internal.codegen.CompilerConstants.Call; 51import jdk.nashorn.internal.ir.debug.JSONWriter; 52import jdk.nashorn.internal.objects.Global; 53import jdk.nashorn.internal.objects.NativeObject; 54import jdk.nashorn.internal.parser.Lexer; 55import jdk.nashorn.internal.runtime.linker.Bootstrap; 56 57 58/** 59 * Utilities to be called by JavaScript runtime API and generated classes. 60 */ 61 62public final class ScriptRuntime { 63 private ScriptRuntime() { 64 } 65 66 /** Singleton representing the empty array object '[]' */ 67 public static final Object[] EMPTY_ARRAY = new Object[0]; 68 69 /** Unique instance of undefined. */ 70 public static final Undefined UNDEFINED = Undefined.getUndefined(); 71 72 /** 73 * Unique instance of undefined used to mark empty array slots. 74 * Can't escape the array. 75 */ 76 public static final Undefined EMPTY = Undefined.getEmpty(); 77 78 /** Method handle to generic + operator, operating on objects */ 79 public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class); 80 81 /** Method handle to generic === operator, operating on objects */ 82 public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class); 83 84 /** Method handle used to enter a {@code with} scope at runtime. */ 85 public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.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 return deflt; 125 } 126 127 /** 128 * Converts a switch tag value to a simple integer. deflt value if it can't. 129 * 130 * @param tag Switch statement tag value. 131 * @param deflt default to use if not convertible. 132 * @return int tag value (or deflt.) 133 */ 134 public static int switchTagAsInt(final boolean tag, final int deflt) { 135 return deflt; 136 } 137 138 /** 139 * Converts a switch tag value to a simple integer. deflt value if it can't. 140 * 141 * @param tag Switch statement tag value. 142 * @param deflt default to use if not convertible. 143 * @return int tag value (or deflt.) 144 */ 145 public static int switchTagAsInt(final long tag, final int deflt) { 146 return isRepresentableAsInt(tag) ? (int)tag : deflt; 147 } 148 149 /** 150 * Converts a switch tag value to a simple integer. deflt value if it can't. 151 * 152 * @param tag Switch statement tag value. 153 * @param deflt default to use if not convertible. 154 * @return int tag value (or deflt.) 155 */ 156 public static int switchTagAsInt(final double tag, final int deflt) { 157 return isRepresentableAsInt(tag) ? (int)tag : deflt; 158 } 159 160 /** 161 * This is the builtin implementation of {@code Object.prototype.toString} 162 * @param self reference 163 * @return string representation as object 164 */ 165 public static String builtinObjectToString(final Object self) { 166 String className; 167 // Spec tells us to convert primitives by ToObject.. 168 // But we don't need to -- all we need is the right class name 169 // of the corresponding primitive wrapper type. 170 171 final JSType type = JSType.ofNoFunction(self); 172 173 switch (type) { 174 case BOOLEAN: 175 className = "Boolean"; 176 break; 177 case NUMBER: 178 className = "Number"; 179 break; 180 case STRING: 181 className = "String"; 182 break; 183 // special case of null and undefined 184 case NULL: 185 className = "Null"; 186 break; 187 case UNDEFINED: 188 className = "Undefined"; 189 break; 190 case OBJECT: 191 if (self instanceof ScriptObject) { 192 className = ((ScriptObject)self).getClassName(); 193 } else if (self instanceof JSObject) { 194 className = ((JSObject)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 JSObject) { 249 return ((JSObject)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 final Object wrapped = Global.instance().wrapAsObject(obj); 261 if (wrapped instanceof ScriptObject) { 262 return ((ScriptObject)wrapped).propertyIterator(); 263 } 264 265 return Collections.emptyIterator(); 266 } 267 268 private static final class RangeIterator implements Iterator<Integer> { 269 private final int length; 270 private int index; 271 272 RangeIterator(final int length) { 273 this.length = length; 274 } 275 276 @Override 277 public boolean hasNext() { 278 return index < length; 279 } 280 281 @Override 282 public Integer next() { 283 return index++; 284 } 285 286 @Override 287 public void remove() { 288 throw new UnsupportedOperationException(); 289 } 290 } 291 292 /** 293 * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS 294 * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over 295 * map values. 296 * @param obj object to iterate on. 297 * @return iterator over the object's property values. 298 */ 299 public static Iterator<?> toValueIterator(final Object obj) { 300 if (obj instanceof ScriptObject) { 301 return ((ScriptObject)obj).valueIterator(); 302 } 303 304 if (obj != null && obj.getClass().isArray()) { 305 final Object array = obj; 306 final int length = Array.getLength(obj); 307 308 return new Iterator<Object>() { 309 private int index = 0; 310 311 @Override 312 public boolean hasNext() { 313 return index < length; 314 } 315 316 @Override 317 public Object next() { 318 if (index >= length) { 319 throw new NoSuchElementException(); 320 } 321 return Array.get(array, index++); 322 } 323 324 @Override 325 public void remove() { 326 throw new UnsupportedOperationException(); 327 } 328 }; 329 } 330 331 if (obj instanceof JSObject) { 332 return ((JSObject)obj).values().iterator(); 333 } 334 335 if (obj instanceof Map) { 336 return ((Map<?,?>)obj).values().iterator(); 337 } 338 339 if (obj instanceof Iterable) { 340 return ((Iterable<?>)obj).iterator(); 341 } 342 343 final Object wrapped = Global.instance().wrapAsObject(obj); 344 if (wrapped instanceof ScriptObject) { 345 return ((ScriptObject)wrapped).valueIterator(); 346 } 347 348 return Collections.emptyIterator(); 349 } 350 351 /** 352 * Merge a scope into its prototype's map. 353 * Merge a scope into its prototype. 354 * 355 * @param scope Scope to merge. 356 * @return prototype object after merge 357 */ 358 public static ScriptObject mergeScope(final ScriptObject scope) { 359 final ScriptObject global = scope.getProto(); 360 global.addBoundProperties(scope); 361 return global; 362 } 363 364 /** 365 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 366 * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker} 367 * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead. 368 * 369 * @param target ScriptFunction object. 370 * @param self Receiver in call. 371 * @param args Call arguments. 372 * @return Call result. 373 */ 374 public static Object apply(final ScriptFunction target, final Object self, final Object... args) { 375 try { 376 return target.invoke(self, args); 377 } catch (final RuntimeException | Error e) { 378 throw e; 379 } catch (final Throwable t) { 380 throw new RuntimeException(t); 381 } 382 } 383 384 /** 385 * Call a script function as a constructor with given args. 386 * 387 * @param target ScriptFunction object. 388 * @param args Call arguments. 389 * @return Constructor call result. 390 */ 391 public static Object construct(final ScriptFunction target, final Object... args) { 392 try { 393 return target.construct(args); 394 } catch (final RuntimeException | Error e) { 395 throw e; 396 } catch (final Throwable t) { 397 throw new RuntimeException(t); 398 } 399 } 400 401 /** 402 * Generic implementation of ECMA 9.12 - SameValue algorithm 403 * 404 * @param x first value to compare 405 * @param y second value to compare 406 * 407 * @return true if both objects have the same value 408 */ 409 public static boolean sameValue(final Object x, final Object y) { 410 final JSType xType = JSType.ofNoFunction(x); 411 final JSType yType = JSType.ofNoFunction(y); 412 413 if (xType != yType) { 414 return false; 415 } 416 417 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 418 return true; 419 } 420 421 if (xType == JSType.NUMBER) { 422 final double xVal = ((Number)x).doubleValue(); 423 final double yVal = ((Number)y).doubleValue(); 424 425 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 426 return true; 427 } 428 429 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 430 if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) { 431 return false; 432 } 433 434 return xVal == yVal; 435 } 436 437 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 438 return x.equals(y); 439 } 440 441 return x == y; 442 } 443 444 /** 445 * Returns AST as JSON compatible string. This is used to 446 * implement "parse" function in resources/parse.js script. 447 * 448 * @param code code to be parsed 449 * @param name name of the code source (used for location) 450 * @param includeLoc tells whether to include location information for nodes or not 451 * @return JSON string representation of AST of the supplied code 452 */ 453 public static String parse(final String code, final String name, final boolean includeLoc) { 454 return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc); 455 } 456 457 /** 458 * Test whether a char is valid JavaScript whitespace 459 * @param ch a char 460 * @return true if valid JavaScript whitespace 461 */ 462 public static boolean isJSWhitespace(final char ch) { 463 return Lexer.isJSWhitespace(ch); 464 } 465 466 /** 467 * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement, 468 * use {@link ScriptObject#getProto()} on the scope. 469 * 470 * @param scope existing scope 471 * @param expression expression in with 472 * 473 * @return {@link WithObject} that is the new scope 474 */ 475 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 476 final Global global = Context.getGlobal(); 477 if (expression == UNDEFINED) { 478 throw typeError(global, "cant.apply.with.to.undefined"); 479 } else if (expression == null) { 480 throw typeError(global, "cant.apply.with.to.null"); 481 } 482 483 if (expression instanceof ScriptObjectMirror) { 484 final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); 485 if (unwrapped instanceof ScriptObject) { 486 return new WithObject(scope, (ScriptObject)unwrapped); 487 } 488 // foreign ScriptObjectMirror 489 final ScriptObject exprObj = global.newObject(); 490 NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); 491 return new WithObject(scope, exprObj); 492 } 493 494 final Object wrappedExpr = JSType.toScriptObject(global, expression); 495 if (wrappedExpr instanceof ScriptObject) { 496 return new WithObject(scope, (ScriptObject)wrappedExpr); 497 } 498 499 throw typeError(global, "cant.apply.with.to.non.scriptobject"); 500 } 501 502 /** 503 * ECMA 11.6.1 - The addition operator (+) - generic implementation 504 * Compiler specializes using {@link jdk.nashorn.internal.codegen.RuntimeCallSite} 505 * if any type information is available for any of the operands 506 * 507 * @param x first term 508 * @param y second term 509 * 510 * @return result of addition 511 */ 512 public static Object ADD(final Object x, final Object y) { 513 // This prefix code to handle Number special is for optimization. 514 final boolean xIsNumber = x instanceof Number; 515 final boolean yIsNumber = y instanceof Number; 516 517 if (xIsNumber && yIsNumber) { 518 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 519 } 520 521 final boolean xIsUndefined = x == UNDEFINED; 522 final boolean yIsUndefined = y == UNDEFINED; 523 524 if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { 525 return Double.NaN; 526 } 527 528 // code below is as per the spec. 529 final Object xPrim = JSType.toPrimitive(x); 530 final Object yPrim = JSType.toPrimitive(y); 531 532 if (xPrim instanceof String || yPrim instanceof String 533 || xPrim instanceof ConsString || yPrim instanceof ConsString) { 534 try { 535 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 536 } catch (final IllegalArgumentException iae) { 537 throw rangeError(iae, "concat.string.too.big"); 538 } 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 if(Global.isLocationPropertyPlaceholder(obj)) { 581 if(CompilerConstants.__LINE__.name().equals(property)) { 582 obj = Integer.valueOf(0); 583 } else { 584 obj = ""; 585 } 586 } 587 } else if (object instanceof Undefined) { 588 obj = ((Undefined)obj).get(property); 589 } else if (object == null) { 590 throw typeError("cant.get.property", safeToString(property), "null"); 591 } else if (JSType.isPrimitive(obj)) { 592 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 593 } else if (obj instanceof JSObject) { 594 obj = ((JSObject)obj).getMember(property.toString()); 595 } else { 596 obj = UNDEFINED; 597 } 598 } 599 600 return JSType.of(obj).typeName(); 601 } 602 603 /** 604 * Throw ReferenceError when LHS of assignment or increment/decrement 605 * operator is not an assignable node (say a literal) 606 * 607 * @param lhs Evaluated LHS 608 * @param rhs Evaluated RHS 609 * @param msg Additional LHS info for error message 610 * @return undefined 611 */ 612 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 613 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 614 } 615 616 /** 617 * ECMA 11.4.1 - delete operation, generic implementation 618 * 619 * @param obj object with property to delete 620 * @param property property to delete 621 * @param strict are we in strict mode 622 * 623 * @return true if property was successfully found and deleted 624 */ 625 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 626 if (obj instanceof ScriptObject) { 627 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 628 } 629 630 if (obj instanceof Undefined) { 631 return ((Undefined)obj).delete(property, false); 632 } 633 634 if (obj == null) { 635 throw typeError("cant.delete.property", safeToString(property), "null"); 636 } 637 638 if (obj instanceof ScriptObjectMirror) { 639 return ((ScriptObjectMirror)obj).delete(property); 640 } 641 642 if (JSType.isPrimitive(obj)) { 643 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 644 } 645 646 if (obj instanceof JSObject) { 647 ((JSObject)obj).removeMember(Objects.toString(property)); 648 return true; 649 } 650 651 // if object is not reference type, vacuously delete is successful. 652 return true; 653 } 654 655 /** 656 * ECMA 11.4.1 - delete operator, special case 657 * 658 * This is 'delete' that always fails. We have to check strict mode and throw error. 659 * That is why this is a runtime function. Or else we could have inlined 'false'. 660 * 661 * @param property property to delete 662 * @param strict are we in strict mode 663 * 664 * @return false always 665 */ 666 public static boolean FAIL_DELETE(final Object property, final Object strict) { 667 if (Boolean.TRUE.equals(strict)) { 668 throw syntaxError("strict.cant.delete", safeToString(property)); 669 } 670 return false; 671 } 672 673 /** 674 * ECMA 11.9.1 - The equals operator (==) - generic implementation 675 * 676 * @param x first object to compare 677 * @param y second object to compare 678 * 679 * @return true if type coerced versions of objects are equal 680 */ 681 public static boolean EQ(final Object x, final Object y) { 682 return equals(x, y); 683 } 684 685 /** 686 * ECMA 11.9.2 - The does-not-equal 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 not equal 692 */ 693 public static boolean NE(final Object x, final Object y) { 694 return !EQ(x, y); 695 } 696 697 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 698 private static boolean equals(final Object x, final Object y) { 699 if (x == y) { 700 return true; 701 } 702 if (x instanceof ScriptObject && y instanceof ScriptObject) { 703 return x == y; 704 } 705 return equalValues(x, y); 706 } 707 708 /** 709 * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value 710 * comparison applies). 711 * @param x one value 712 * @param y another value 713 * @return true if they're equal according to 11.9.3 714 */ 715 private static boolean equalValues(final Object x, final Object y) { 716 final JSType xType = JSType.ofNoFunction(x); 717 final JSType yType = JSType.ofNoFunction(y); 718 719 if (xType == yType) { 720 return equalSameTypeValues(x, y, xType); 721 } 722 723 return equalDifferentTypeValues(x, y, xType, yType); 724 } 725 726 /** 727 * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares 728 * values belonging to the same JSType. 729 * @param x one value 730 * @param y another value 731 * @param type the common type for the values 732 * @return true if they're equal 733 */ 734 private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { 735 if (type == JSType.UNDEFINED || type == JSType.NULL) { 736 return true; 737 } 738 739 if (type == JSType.NUMBER) { 740 return ((Number)x).doubleValue() == ((Number)y).doubleValue(); 741 } 742 743 if (type == JSType.STRING) { 744 // String may be represented by ConsString 745 return x.toString().equals(y.toString()); 746 } 747 748 if (type == JSType.BOOLEAN) { 749 return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); 750 } 751 752 return x == y; 753 } 754 755 /** 756 * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. 757 * @param x one value 758 * @param y another value 759 * @param xType the type for the value x 760 * @param yType the type for the value y 761 * @return true if they're equal 762 */ 763 private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { 764 if (xType == JSType.UNDEFINED && yType == JSType.NULL || xType == JSType.NULL && yType == JSType.UNDEFINED) { 765 return true; 766 } 767 768 if (xType == JSType.NUMBER && yType == JSType.STRING) { 769 return equals(x, JSType.toNumber(y)); 770 } 771 772 if (xType == JSType.STRING && yType == JSType.NUMBER) { 773 return equals(JSType.toNumber(x), y); 774 } 775 776 if (xType == JSType.BOOLEAN) { 777 return equals(JSType.toNumber(x), y); 778 } 779 780 if (yType == JSType.BOOLEAN) { 781 return equals(x, JSType.toNumber(y)); 782 } 783 784 if ((xType == JSType.STRING || xType == JSType.NUMBER) && y instanceof ScriptObject) { 785 return equals(x, JSType.toPrimitive(y)); 786 } 787 788 if (x instanceof ScriptObject && (yType == JSType.STRING || yType == JSType.NUMBER)) { 789 return equals(JSType.toPrimitive(x), y); 790 } 791 792 return false; 793 } 794 795 /** 796 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 797 * 798 * @param x first object to compare 799 * @param y second object to compare 800 * 801 * @return true if objects are equal 802 */ 803 public static boolean EQ_STRICT(final Object x, final Object y) { 804 return strictEquals(x, y); 805 } 806 807 /** 808 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 809 * 810 * @param x first object to compare 811 * @param y second object to compare 812 * 813 * @return true if objects are not equal 814 */ 815 public static boolean NE_STRICT(final Object x, final Object y) { 816 return !EQ_STRICT(x, y); 817 } 818 819 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 820 private static boolean strictEquals(final Object x, final Object y) { 821 // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having 822 // NaN value is not equal to itself by value even though it is referentially. 823 824 final JSType xType = JSType.ofNoFunction(x); 825 final JSType yType = JSType.ofNoFunction(y); 826 827 if (xType != yType) { 828 return false; 829 } 830 831 return equalSameTypeValues(x, y, xType); 832 } 833 834 /** 835 * ECMA 11.8.6 - The in operator - generic implementation 836 * 837 * @param property property to check for 838 * @param obj object in which to check for property 839 * 840 * @return true if objects are equal 841 */ 842 public static boolean IN(final Object property, final Object obj) { 843 final JSType rvalType = JSType.ofNoFunction(obj); 844 845 if (rvalType == JSType.OBJECT) { 846 if (obj instanceof ScriptObject) { 847 return ((ScriptObject)obj).has(property); 848 } 849 850 if (obj instanceof JSObject) { 851 return ((JSObject)obj).hasMember(Objects.toString(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 JSObject) { 881 return ((JSObject)clazz).isInstance(obj); 882 } 883 884 // provide for reverse hook 885 if (obj instanceof JSObject) { 886 return ((JSObject)obj).isInstanceOf(clazz); 887 } 888 889 throw typeError("instanceof.on.non.object"); 890 } 891 892 /** 893 * ECMA 11.8.1 - The less 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 less than y 899 */ 900 public static boolean LT(final Object x, final Object y) { 901 final Object value = lessThan(x, y, true); 902 return value == UNDEFINED ? false : (Boolean)value; 903 } 904 905 /** 906 * ECMA 11.8.2 - The greater than 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 greater than y 912 */ 913 public static boolean GT(final Object x, final Object y) { 914 final Object value = lessThan(y, x, false); 915 return value == UNDEFINED ? false : (Boolean)value; 916 } 917 918 /** 919 * ECMA 11.8.3 - The less 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 less than or equal to y 925 */ 926 public static boolean LE(final Object x, final Object y) { 927 final Object value = lessThan(y, x, false); 928 return !(Boolean.TRUE.equals(value) || value == UNDEFINED); 929 } 930 931 /** 932 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 933 * 934 * @param x first object to compare 935 * @param y second object to compare 936 * 937 * @return true if x is greater than or equal to y 938 */ 939 public static boolean GE(final Object x, final Object y) { 940 final Object value = lessThan(x, y, true); 941 return !(Boolean.TRUE.equals(value) || value == UNDEFINED); 942 } 943 944 /** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */ 945 private static Object lessThan(final Object x, final Object y, final boolean leftFirst) { 946 Object px, py; 947 948 //support e.g. x < y should throw exception correctly if x or y are not numeric 949 if (leftFirst) { 950 px = JSType.toPrimitive(x, Number.class); 951 py = JSType.toPrimitive(y, Number.class); 952 } else { 953 py = JSType.toPrimitive(y, Number.class); 954 px = JSType.toPrimitive(x, Number.class); 955 } 956 957 if (JSType.ofNoFunction(px) == JSType.STRING && JSType.ofNoFunction(py) == JSType.STRING) { 958 // May be String or ConsString 959 return px.toString().compareTo(py.toString()) < 0; 960 } 961 962 final double nx = JSType.toNumber(px); 963 final double ny = JSType.toNumber(py); 964 965 if (Double.isNaN(nx) || Double.isNaN(ny)) { 966 return UNDEFINED; 967 } 968 969 if (nx == ny) { 970 return false; 971 } 972 973 if (nx > 0 && ny > 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { 974 return false; 975 } 976 977 if (nx < 0 && ny < 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { 978 return false; 979 } 980 981 return nx < ny; 982 } 983 984} 985