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