1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 30 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 31 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 32 import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; 33 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 35 import static jdk.nashorn.internal.runtime.JSType.isString; 36 37 import java.lang.invoke.MethodHandle; 38 import java.lang.invoke.MethodHandles; 39 import java.lang.invoke.SwitchPoint; 40 import java.lang.reflect.Array; 41 import java.util.Collections; 42 import java.util.Iterator; 43 import java.util.List; 44 import java.util.Locale; 45 import java.util.Map; 46 import java.util.NoSuchElementException; 47 import java.util.Objects; 48 import jdk.internal.dynalink.beans.StaticClass; 49 import jdk.nashorn.api.scripting.JSObject; 50 import jdk.nashorn.api.scripting.ScriptObjectMirror; 51 import jdk.nashorn.internal.codegen.ApplySpecialization; 52 import jdk.nashorn.internal.codegen.CompilerConstants; 53 import jdk.nashorn.internal.codegen.CompilerConstants.Call; 54 import jdk.nashorn.internal.ir.debug.JSONWriter; 55 import jdk.nashorn.internal.objects.Global; 56 import jdk.nashorn.internal.objects.NativeObject; 57 import jdk.nashorn.internal.parser.Lexer; 58 import jdk.nashorn.internal.runtime.linker.Bootstrap; 59 60 /** 61 * Utilities to be called by JavaScript runtime API and generated classes. 62 */ 63 64 public 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 proto = scope.getProto(); 377 // Variable declarations go into the first parent scope that is not a with-statement 378 ScriptObject parentScope = proto; 379 while (parentScope instanceof WithObject) { 380 parentScope = parentScope.getProto(); 381 } 382 parentScope.addBoundProperties(scope); 383 return proto; 384 } 385 386 /** 387 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 388 * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker} 389 * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead. 390 * 391 * @param target ScriptFunction object. 392 * @param self Receiver in call. 393 * @param args Call arguments. 394 * @return Call result. 395 */ 396 public static Object apply(final ScriptFunction target, final Object self, final Object... args) { 397 try { 398 return target.invoke(self, args); 399 } catch (final RuntimeException | Error e) { 400 throw e; 401 } catch (final Throwable t) { 402 throw new RuntimeException(t); 403 } 404 } 405 406 /** 407 * Throws a reference error for an undefined variable. 408 * 409 * @param name the variable name 410 */ 411 public static void throwReferenceError(final String name) { 412 throw referenceError("not.defined", name); 413 } 414 415 /** 416 * Throws a type error for an assignment to a const. 417 * 418 * @param name the const name 419 */ 420 public static void throwConstTypeError(final String name) { 421 throw typeError("assign.constant", name); 422 } 423 424 /** 425 * Call a script function as a constructor with given args. 426 * 427 * @param target ScriptFunction object. 428 * @param args Call arguments. 429 * @return Constructor call result. 430 */ 431 public static Object construct(final ScriptFunction target, final Object... args) { 432 try { 433 return target.construct(args); 434 } catch (final RuntimeException | Error e) { 435 throw e; 436 } catch (final Throwable t) { 437 throw new RuntimeException(t); 438 } 439 } 440 441 /** 442 * Generic implementation of ECMA 9.12 - SameValue algorithm 443 * 444 * @param x first value to compare 445 * @param y second value to compare 446 * 447 * @return true if both objects have the same value 448 */ 449 public static boolean sameValue(final Object x, final Object y) { 450 final JSType xType = JSType.ofNoFunction(x); 451 final JSType yType = JSType.ofNoFunction(y); 452 453 if (xType != yType) { 454 return false; 455 } 456 457 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 458 return true; 459 } 460 461 if (xType == JSType.NUMBER) { 462 final double xVal = ((Number)x).doubleValue(); 463 final double yVal = ((Number)y).doubleValue(); 464 465 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 466 return true; 467 } 468 469 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 470 if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) { 471 return false; 472 } 473 474 return xVal == yVal; 475 } 476 477 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 478 return x.equals(y); 479 } 480 481 return x == y; 482 } 483 484 /** 485 * Returns AST as JSON compatible string. This is used to 486 * implement "parse" function in resources/parse.js script. 487 * 488 * @param code code to be parsed 489 * @param name name of the code source (used for location) 490 * @param includeLoc tells whether to include location information for nodes or not 491 * @return JSON string representation of AST of the supplied code 492 */ 493 public static String parse(final String code, final String name, final boolean includeLoc) { 494 return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc); 495 } 496 497 /** 498 * Test whether a char is valid JavaScript whitespace 499 * @param ch a char 500 * @return true if valid JavaScript whitespace 501 */ 502 public static boolean isJSWhitespace(final char ch) { 503 return Lexer.isJSWhitespace(ch); 504 } 505 506 /** 507 * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement, 508 * use {@link ScriptObject#getProto()} on the scope. 509 * 510 * @param scope existing scope 511 * @param expression expression in with 512 * 513 * @return {@link WithObject} that is the new scope 514 */ 515 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 516 final Global global = Context.getGlobal(); 517 if (expression == UNDEFINED) { 518 throw typeError(global, "cant.apply.with.to.undefined"); 519 } else if (expression == null) { 520 throw typeError(global, "cant.apply.with.to.null"); 521 } 522 523 if (expression instanceof ScriptObjectMirror) { 524 final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); 525 if (unwrapped instanceof ScriptObject) { 526 return new WithObject(scope, (ScriptObject)unwrapped); 527 } 528 // foreign ScriptObjectMirror 529 final ScriptObject exprObj = global.newObject(); 530 NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); 531 return new WithObject(scope, exprObj); 532 } 533 534 final Object wrappedExpr = JSType.toScriptObject(global, expression); 535 if (wrappedExpr instanceof ScriptObject) { 536 return new WithObject(scope, (ScriptObject)wrappedExpr); 537 } 538 539 throw typeError(global, "cant.apply.with.to.non.scriptobject"); 540 } 541 542 /** 543 * ECMA 11.6.1 - The addition operator (+) - generic implementation 544 * 545 * @param x first term 546 * @param y second term 547 * 548 * @return result of addition 549 */ 550 public static Object ADD(final Object x, final Object y) { 551 // This prefix code to handle Number special is for optimization. 552 final boolean xIsNumber = x instanceof Number; 553 final boolean yIsNumber = y instanceof Number; 554 555 if (xIsNumber && yIsNumber) { 556 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 557 } 558 559 final boolean xIsUndefined = x == UNDEFINED; 560 final boolean yIsUndefined = y == UNDEFINED; 561 562 if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { 563 return Double.NaN; 564 } 565 566 // code below is as per the spec. 567 final Object xPrim = JSType.toPrimitive(x); 568 final Object yPrim = JSType.toPrimitive(y); 569 570 if (isString(xPrim) || isString(yPrim)) { 571 try { 572 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 573 } catch (final IllegalArgumentException iae) { 574 throw rangeError(iae, "concat.string.too.big"); 575 } 576 } 577 578 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 579 } 580 581 /** 582 * Debugger hook. 583 * TODO: currently unimplemented 584 * 585 * @return undefined 586 */ 587 public static Object DEBUGGER() { 588 return UNDEFINED; 589 } 590 591 /** 592 * New hook 593 * 594 * @param clazz type for the clss 595 * @param args constructor arguments 596 * 597 * @return undefined 598 */ 599 public static Object NEW(final Object clazz, final Object... args) { 600 return UNDEFINED; 601 } 602 603 /** 604 * ECMA 11.4.3 The typeof Operator - generic implementation 605 * 606 * @param object the object from which to retrieve property to type check 607 * @param property property in object to check 608 * 609 * @return type name 610 */ 611 public static Object TYPEOF(final Object object, final Object property) { 612 Object obj = object; 613 614 if (property != null) { 615 if (obj instanceof ScriptObject) { 616 obj = ((ScriptObject)obj).get(property); 617 if(Global.isLocationPropertyPlaceholder(obj)) { 618 if(CompilerConstants.__LINE__.name().equals(property)) { 619 obj = Integer.valueOf(0); 620 } else { 621 obj = ""; 622 } 623 } 624 } else if (object instanceof Undefined) { 625 obj = ((Undefined)obj).get(property); 626 } else if (object == null) { 627 throw typeError("cant.get.property", safeToString(property), "null"); 628 } else if (JSType.isPrimitive(obj)) { 629 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 630 } else if (obj instanceof JSObject) { 631 obj = ((JSObject)obj).getMember(property.toString()); 632 } else { 633 obj = UNDEFINED; 634 } 635 } 636 637 return JSType.of(obj).typeName(); 638 } 639 640 /** 641 * Throw ReferenceError when LHS of assignment or increment/decrement 642 * operator is not an assignable node (say a literal) 643 * 644 * @param lhs Evaluated LHS 645 * @param rhs Evaluated RHS 646 * @param msg Additional LHS info for error message 647 * @return undefined 648 */ 649 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 650 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 651 } 652 653 /** 654 * ECMA 11.4.1 - delete operation, generic implementation 655 * 656 * @param obj object with property to delete 657 * @param property property to delete 658 * @param strict are we in strict mode 659 * 660 * @return true if property was successfully found and deleted 661 */ 662 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 663 if (obj instanceof ScriptObject) { 664 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 665 } 666 667 if (obj instanceof Undefined) { 668 return ((Undefined)obj).delete(property, false); 669 } 670 671 if (obj == null) { 672 throw typeError("cant.delete.property", safeToString(property), "null"); 673 } 674 675 if (obj instanceof ScriptObjectMirror) { 676 return ((ScriptObjectMirror)obj).delete(property); 677 } 678 679 if (JSType.isPrimitive(obj)) { 680 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 681 } 682 683 if (obj instanceof JSObject) { 684 ((JSObject)obj).removeMember(Objects.toString(property)); 685 return true; 686 } 687 688 // if object is not reference type, vacuously delete is successful. 689 return true; 690 } 691 692 /** 693 * ECMA 11.4.1 - delete operator, special case 694 * 695 * This is 'delete' that always fails. We have to check strict mode and throw error. 696 * That is why this is a runtime function. Or else we could have inlined 'false'. 697 * 698 * @param property property to delete 699 * @param strict are we in strict mode 700 * 701 * @return false always 702 */ 703 public static boolean FAIL_DELETE(final Object property, final Object strict) { 704 if (Boolean.TRUE.equals(strict)) { 705 throw syntaxError("strict.cant.delete", safeToString(property)); 706 } 707 return false; 708 } 709 710 /** 711 * ECMA 11.9.1 - The equals operator (==) - generic implementation 712 * 713 * @param x first object to compare 714 * @param y second object to compare 715 * 716 * @return true if type coerced versions of objects are equal 717 */ 718 public static boolean EQ(final Object x, final Object y) { 719 return equals(x, y); 720 } 721 722 /** 723 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 724 * 725 * @param x first object to compare 726 * @param y second object to compare 727 * 728 * @return true if type coerced versions of objects are not equal 729 */ 730 public static boolean NE(final Object x, final Object y) { 731 return !EQ(x, y); 732 } 733 734 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 735 private static boolean equals(final Object x, final Object y) { 736 if (x == y) { 737 return true; 738 } 739 if (x instanceof ScriptObject && y instanceof ScriptObject) { 740 return false; // x != y 741 } 742 if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) { 743 return ScriptObjectMirror.identical(x, y); 744 } 745 return equalValues(x, y); 746 } 747 748 /** 749 * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value 750 * comparison applies). 751 * @param x one value 752 * @param y another value 753 * @return true if they're equal according to 11.9.3 754 */ 755 private static boolean equalValues(final Object x, final Object y) { 756 final JSType xType = JSType.ofNoFunction(x); 757 final JSType yType = JSType.ofNoFunction(y); 758 759 if (xType == yType) { 760 return equalSameTypeValues(x, y, xType); 761 } 762 763 return equalDifferentTypeValues(x, y, xType, yType); 764 } 765 766 /** 767 * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares 768 * values belonging to the same JSType. 769 * @param x one value 770 * @param y another value 771 * @param type the common type for the values 772 * @return true if they're equal 773 */ 774 private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { 775 if (type == JSType.UNDEFINED || type == JSType.NULL) { 776 return true; 777 } 778 779 if (type == JSType.NUMBER) { 780 return ((Number)x).doubleValue() == ((Number)y).doubleValue(); 781 } 782 783 if (type == JSType.STRING) { 784 // String may be represented by ConsString 785 return x.toString().equals(y.toString()); 786 } 787 788 if (type == JSType.BOOLEAN) { 789 return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); 790 } 791 792 return x == y; 793 } 794 795 /** 796 * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. 797 * @param x one value 798 * @param y another value 799 * @param xType the type for the value x 800 * @param yType the type for the value y 801 * @return true if they're equal 802 */ 803 private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { 804 if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) { 805 return true; 806 } else if (isNumberAndString(xType, yType)) { 807 return equalNumberToString(x, y); 808 } else if (isNumberAndString(yType, xType)) { 809 // Can reverse order as both are primitives 810 return equalNumberToString(y, x); 811 } else if (xType == JSType.BOOLEAN) { 812 return equalBooleanToAny(x, y); 813 } else if (yType == JSType.BOOLEAN) { 814 // Can reverse order as y is primitive 815 return equalBooleanToAny(y, x); 816 } else if (isNumberOrStringAndObject(xType, yType)) { 817 return equalNumberOrStringToObject(x, y); 818 } else if (isNumberOrStringAndObject(yType, xType)) { 819 // Can reverse order as y is primitive 820 return equalNumberOrStringToObject(y, x); 821 } 822 823 return false; 824 } 825 826 private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) { 827 return xType == JSType.UNDEFINED && yType == JSType.NULL; 828 } 829 830 private static boolean isNumberAndString(final JSType xType, final JSType yType) { 831 return xType == JSType.NUMBER && yType == JSType.STRING; 832 } 833 834 private static boolean isNumberOrStringAndObject(final JSType xType, final JSType yType) { 835 return (xType == JSType.NUMBER || xType == JSType.STRING) && yType == JSType.OBJECT; 836 } 837 838 private static boolean equalNumberToString(final Object num, final Object str) { 839 // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We 840 // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number 841 // comparison. 842 return ((Number)num).doubleValue() == JSType.toNumber(str.toString()); 843 } 844 845 private static boolean equalBooleanToAny(final Object bool, final Object any) { 846 return equals(JSType.toNumber((Boolean)bool), any); 847 } 848 849 private static boolean equalNumberOrStringToObject(final Object numOrStr, final Object any) { 850 return equals(numOrStr, JSType.toPrimitive(any)); 851 } 852 853 /** 854 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 855 * 856 * @param x first object to compare 857 * @param y second object to compare 858 * 859 * @return true if objects are equal 860 */ 861 public static boolean EQ_STRICT(final Object x, final Object y) { 862 return strictEquals(x, y); 863 } 864 865 /** 866 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 867 * 868 * @param x first object to compare 869 * @param y second object to compare 870 * 871 * @return true if objects are not equal 872 */ 873 public static boolean NE_STRICT(final Object x, final Object y) { 874 return !EQ_STRICT(x, y); 875 } 876 877 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 878 private static boolean strictEquals(final Object x, final Object y) { 879 // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having 880 // NaN value is not equal to itself by value even though it is referentially. 881 882 final JSType xType = JSType.ofNoFunction(x); 883 final JSType yType = JSType.ofNoFunction(y); 884 885 if (xType != yType) { 886 return false; 887 } 888 889 return equalSameTypeValues(x, y, xType); 890 } 891 892 /** 893 * ECMA 11.8.6 - The in operator - generic implementation 894 * 895 * @param property property to check for 896 * @param obj object in which to check for property 897 * 898 * @return true if objects are equal 899 */ 900 public static boolean IN(final Object property, final Object obj) { 901 final JSType rvalType = JSType.ofNoFunction(obj); 902 903 if (rvalType == JSType.OBJECT) { 904 if (obj instanceof ScriptObject) { 905 return ((ScriptObject)obj).has(property); 906 } 907 908 if (obj instanceof JSObject) { 909 return ((JSObject)obj).hasMember(Objects.toString(property)); 910 } 911 912 return false; 913 } 914 915 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 916 } 917 918 /** 919 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 920 * 921 * @param obj first object to compare 922 * @param clazz type to check against 923 * 924 * @return true if {@code obj} is an instanceof {@code clazz} 925 */ 926 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 927 if (clazz instanceof ScriptFunction) { 928 if (obj instanceof ScriptObject) { 929 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 930 } 931 return false; 932 } 933 934 if (clazz instanceof StaticClass) { 935 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 936 } 937 938 if (clazz instanceof JSObject) { 939 return ((JSObject)clazz).isInstance(obj); 940 } 941 942 // provide for reverse hook 943 if (obj instanceof JSObject) { 944 return ((JSObject)obj).isInstanceOf(clazz); 945 } 946 947 throw typeError("instanceof.on.non.object"); 948 } 949 950 /** 951 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 952 * 953 * @param x first object to compare 954 * @param y second object to compare 955 * 956 * @return true if x is less than y 957 */ 958 public static boolean LT(final Object x, final Object y) { 959 final Object px = JSType.toPrimitive(x, Number.class); 960 final Object py = JSType.toPrimitive(y, Number.class); 961 962 return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : 963 JSType.toNumber(px) < JSType.toNumber(py); 964 } 965 966 private static boolean areBothString(final Object x, final Object y) { 967 return isString(x) && isString(y); 968 } 969 970 /** 971 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 972 * 973 * @param x first object to compare 974 * @param y second object to compare 975 * 976 * @return true if x is greater than y 977 */ 978 public static boolean GT(final Object x, final Object y) { 979 final Object px = JSType.toPrimitive(x, Number.class); 980 final Object py = JSType.toPrimitive(y, Number.class); 981 982 return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : 983 JSType.toNumber(px) > JSType.toNumber(py); 984 } 985 986 /** 987 * ECMA 11.8.3 - The less 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 less than or equal to y 993 */ 994 public static boolean LE(final Object x, final Object y) { 995 final Object px = JSType.toPrimitive(x, Number.class); 996 final Object py = JSType.toPrimitive(y, Number.class); 997 998 return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : 999 JSType.toNumber(px) <= JSType.toNumber(py); 1000 } 1001 1002 /** 1003 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 1004 * 1005 * @param x first object to compare 1006 * @param y second object to compare 1007 * 1008 * @return true if x is greater than or equal to y 1009 */ 1010 public static boolean GE(final Object x, final Object y) { 1011 final Object px = JSType.toPrimitive(x, Number.class); 1012 final Object py = JSType.toPrimitive(y, Number.class); 1013 1014 return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : 1015 JSType.toNumber(px) >= JSType.toNumber(py); 1016 } 1017 1018 /** 1019 * Tag a reserved name as invalidated - used when someone writes 1020 * to a property with this name - overly conservative, but link time 1021 * is too late to apply e.g. apply->call specialization 1022 * @param name property name 1023 */ 1024 public static void invalidateReservedBuiltinName(final String name) { 1025 final Context context = Context.getContextTrusted(); 1026 final SwitchPoint sp = context.getBuiltinSwitchPoint(name); 1027 assert sp != null; 1028 context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); 1029 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 1030 } 1031 }