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