1 /* 2 * Copyright (c) 2010, 2015, 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.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 parentScope = scope.getProto(); 377 parentScope.addBoundProperties(scope); 378 return parentScope; 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 creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)} 384 * 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 * 540 * @param x first term 541 * @param y second term 542 * 543 * @return result of addition 544 */ 545 public static Object ADD(final Object x, final Object y) { 546 // This prefix code to handle Number special is for optimization. 547 final boolean xIsNumber = x instanceof Number; 548 final boolean yIsNumber = y instanceof Number; 549 550 if (xIsNumber && yIsNumber) { 551 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 552 } 553 554 final boolean xIsUndefined = x == UNDEFINED; 555 final boolean yIsUndefined = y == UNDEFINED; 556 557 if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { 558 return Double.NaN; 559 } 560 561 // code below is as per the spec. 562 final Object xPrim = JSType.toPrimitive(x); 563 final Object yPrim = JSType.toPrimitive(y); 564 565 if (isString(xPrim) || isString(yPrim)) { 566 try { 567 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 568 } catch (final IllegalArgumentException iae) { 569 throw rangeError(iae, "concat.string.too.big"); 570 } 571 } 572 573 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 574 } 575 576 /** 577 * Debugger hook. 578 * TODO: currently unimplemented 579 * 580 * @return undefined 581 */ 582 public static Object DEBUGGER() { 583 return UNDEFINED; 584 } 585 586 /** 587 * New hook 588 * 589 * @param clazz type for the clss 590 * @param args constructor arguments 591 * 592 * @return undefined 593 */ 594 public static Object NEW(final Object clazz, final Object... args) { 595 return UNDEFINED; 596 } 597 598 /** 599 * ECMA 11.4.3 The typeof Operator - generic implementation 600 * 601 * @param object the object from which to retrieve property to type check 602 * @param property property in object to check 603 * 604 * @return type name 605 */ 606 public static Object TYPEOF(final Object object, final Object property) { 607 Object obj = object; 608 609 if (property != null) { 610 if (obj instanceof ScriptObject) { 611 obj = ((ScriptObject)obj).get(property); 612 if(Global.isLocationPropertyPlaceholder(obj)) { 613 if(CompilerConstants.__LINE__.name().equals(property)) { 614 obj = 0; 615 } else { 616 obj = ""; 617 } 618 } 619 } else if (object instanceof Undefined) { 620 obj = ((Undefined)obj).get(property); 621 } else if (object == null) { 622 throw typeError("cant.get.property", safeToString(property), "null"); 623 } else if (JSType.isPrimitive(obj)) { 624 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 625 } else if (obj instanceof JSObject) { 626 obj = ((JSObject)obj).getMember(property.toString()); 627 } else { 628 obj = UNDEFINED; 629 } 630 } 631 632 return JSType.of(obj).typeName(); 633 } 634 635 /** 636 * Throw ReferenceError when LHS of assignment or increment/decrement 637 * operator is not an assignable node (say a literal) 638 * 639 * @param lhs Evaluated LHS 640 * @param rhs Evaluated RHS 641 * @param msg Additional LHS info for error message 642 * @return undefined 643 */ 644 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 645 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 646 } 647 648 /** 649 * ECMA 11.4.1 - delete operation, generic implementation 650 * 651 * @param obj object with property to delete 652 * @param property property to delete 653 * @param strict are we in strict mode 654 * 655 * @return true if property was successfully found and deleted 656 */ 657 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 658 if (obj instanceof ScriptObject) { 659 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 660 } 661 662 if (obj instanceof Undefined) { 663 return ((Undefined)obj).delete(property, false); 664 } 665 666 if (obj == null) { 667 throw typeError("cant.delete.property", safeToString(property), "null"); 668 } 669 670 if (obj instanceof ScriptObjectMirror) { 671 return ((ScriptObjectMirror)obj).delete(property); 672 } 673 674 if (JSType.isPrimitive(obj)) { 675 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 676 } 677 678 if (obj instanceof JSObject) { 679 ((JSObject)obj).removeMember(Objects.toString(property)); 680 return true; 681 } 682 683 // if object is not reference type, vacuously delete is successful. 684 return true; 685 } 686 687 /** 688 * ECMA 11.4.1 - delete operator, implementation for slow scopes 689 * 690 * This implementation of 'delete' walks the scope chain to find the scope that contains the 691 * property to be deleted, then invokes delete on it. 692 * 693 * @param obj top scope object 694 * @param property property to delete 695 * @param strict are we in strict mode 696 * 697 * @return true if property was successfully found and deleted 698 */ 699 public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) { 700 if (obj instanceof ScriptObject) { 701 ScriptObject sobj = (ScriptObject) obj; 702 final String key = property.toString(); 703 while (sobj != null && sobj.isScope()) { 704 final FindProperty find = sobj.findProperty(key, false); 705 if (find != null) { 706 return sobj.delete(key, Boolean.TRUE.equals(strict)); 707 } 708 sobj = sobj.getProto(); 709 } 710 } 711 return DELETE(obj, property, strict); 712 } 713 714 /** 715 * ECMA 11.4.1 - delete operator, special case 716 * 717 * This is 'delete' that always fails. We have to check strict mode and throw error. 718 * That is why this is a runtime function. Or else we could have inlined 'false'. 719 * 720 * @param property property to delete 721 * @param strict are we in strict mode 722 * 723 * @return false always 724 */ 725 public static boolean FAIL_DELETE(final Object property, final Object strict) { 726 if (Boolean.TRUE.equals(strict)) { 727 throw syntaxError("strict.cant.delete", safeToString(property)); 728 } 729 return false; 730 } 731 732 /** 733 * ECMA 11.9.1 - The equals operator (==) - generic implementation 734 * 735 * @param x first object to compare 736 * @param y second object to compare 737 * 738 * @return true if type coerced versions of objects are equal 739 */ 740 public static boolean EQ(final Object x, final Object y) { 741 return equals(x, y); 742 } 743 744 /** 745 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 746 * 747 * @param x first object to compare 748 * @param y second object to compare 749 * 750 * @return true if type coerced versions of objects are not equal 751 */ 752 public static boolean NE(final Object x, final Object y) { 753 return !EQ(x, y); 754 } 755 756 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 757 private static boolean equals(final Object x, final Object y) { 758 // We want to keep this method small so we skip reference equality check for numbers 759 // as NaN should return false when compared to itself (JDK-8043608). 760 if (x == y && !(x instanceof Number)) { 761 return true; 762 } 763 if (x instanceof ScriptObject && y instanceof ScriptObject) { 764 return false; // x != y 765 } 766 if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) { 767 return ScriptObjectMirror.identical(x, y); 768 } 769 return equalValues(x, y); 770 } 771 772 /** 773 * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value 774 * comparison applies). 775 * @param x one value 776 * @param y another value 777 * @return true if they're equal according to 11.9.3 778 */ 779 private static boolean equalValues(final Object x, final Object y) { 780 final JSType xType = JSType.ofNoFunction(x); 781 final JSType yType = JSType.ofNoFunction(y); 782 783 if (xType == yType) { 784 return equalSameTypeValues(x, y, xType); 785 } 786 787 return equalDifferentTypeValues(x, y, xType, yType); 788 } 789 790 /** 791 * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares 792 * values belonging to the same JSType. 793 * @param x one value 794 * @param y another value 795 * @param type the common type for the values 796 * @return true if they're equal 797 */ 798 private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { 799 if (type == JSType.UNDEFINED || type == JSType.NULL) { 800 return true; 801 } 802 803 if (type == JSType.NUMBER) { 804 return ((Number)x).doubleValue() == ((Number)y).doubleValue(); 805 } 806 807 if (type == JSType.STRING) { 808 // String may be represented by ConsString 809 return x.toString().equals(y.toString()); 810 } 811 812 if (type == JSType.BOOLEAN) { 813 return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); 814 } 815 816 return x == y; 817 } 818 819 /** 820 * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. 821 * @param x one value 822 * @param y another value 823 * @param xType the type for the value x 824 * @param yType the type for the value y 825 * @return true if they're equal 826 */ 827 private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { 828 if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) { 829 return true; 830 } else if (isNumberAndString(xType, yType)) { 831 return equalNumberToString(x, y); 832 } else if (isNumberAndString(yType, xType)) { 833 // Can reverse order as both are primitives 834 return equalNumberToString(y, x); 835 } else if (xType == JSType.BOOLEAN) { 836 return equalBooleanToAny(x, y); 837 } else if (yType == JSType.BOOLEAN) { 838 // Can reverse order as y is primitive 839 return equalBooleanToAny(y, x); 840 } else if (isPrimitiveAndObject(xType, yType)) { 841 return equalWrappedPrimitiveToObject(x, y); 842 } else if (isPrimitiveAndObject(yType, xType)) { 843 // Can reverse order as y is primitive 844 return equalWrappedPrimitiveToObject(y, x); 845 } 846 847 return false; 848 } 849 850 private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) { 851 return xType == JSType.UNDEFINED && yType == JSType.NULL; 852 } 853 854 private static boolean isNumberAndString(final JSType xType, final JSType yType) { 855 return xType == JSType.NUMBER && yType == JSType.STRING; 856 } 857 858 private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) { 859 return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT; 860 } 861 862 private static boolean equalNumberToString(final Object num, final Object str) { 863 // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We 864 // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number 865 // comparison. 866 return ((Number)num).doubleValue() == JSType.toNumber(str.toString()); 867 } 868 869 private static boolean equalBooleanToAny(final Object bool, final Object any) { 870 return equals(JSType.toNumber((Boolean)bool), any); 871 } 872 873 private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) { 874 return equals(numOrStr, JSType.toPrimitive(any)); 875 } 876 877 /** 878 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 879 * 880 * @param x first object to compare 881 * @param y second object to compare 882 * 883 * @return true if objects are equal 884 */ 885 public static boolean EQ_STRICT(final Object x, final Object y) { 886 return strictEquals(x, y); 887 } 888 889 /** 890 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 891 * 892 * @param x first object to compare 893 * @param y second object to compare 894 * 895 * @return true if objects are not equal 896 */ 897 public static boolean NE_STRICT(final Object x, final Object y) { 898 return !EQ_STRICT(x, y); 899 } 900 901 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 902 private static boolean strictEquals(final Object x, final Object y) { 903 // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having 904 // NaN value is not equal to itself by value even though it is referentially. 905 906 final JSType xType = JSType.ofNoFunction(x); 907 final JSType yType = JSType.ofNoFunction(y); 908 909 if (xType != yType) { 910 return false; 911 } 912 913 return equalSameTypeValues(x, y, xType); 914 } 915 916 /** 917 * ECMA 11.8.6 - The in operator - generic implementation 918 * 919 * @param property property to check for 920 * @param obj object in which to check for property 921 * 922 * @return true if objects are equal 923 */ 924 public static boolean IN(final Object property, final Object obj) { 925 final JSType rvalType = JSType.ofNoFunction(obj); 926 927 if (rvalType == JSType.OBJECT) { 928 if (obj instanceof ScriptObject) { 929 return ((ScriptObject)obj).has(property); 930 } 931 932 if (obj instanceof JSObject) { 933 return ((JSObject)obj).hasMember(Objects.toString(property)); 934 } 935 936 return false; 937 } 938 939 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 940 } 941 942 /** 943 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 944 * 945 * @param obj first object to compare 946 * @param clazz type to check against 947 * 948 * @return true if {@code obj} is an instanceof {@code clazz} 949 */ 950 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 951 if (clazz instanceof ScriptFunction) { 952 if (obj instanceof ScriptObject) { 953 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 954 } 955 return false; 956 } 957 958 if (clazz instanceof StaticClass) { 959 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 960 } 961 962 if (clazz instanceof JSObject) { 963 return ((JSObject)clazz).isInstance(obj); 964 } 965 966 // provide for reverse hook 967 if (obj instanceof JSObject) { 968 return ((JSObject)obj).isInstanceOf(clazz); 969 } 970 971 throw typeError("instanceof.on.non.object"); 972 } 973 974 /** 975 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 976 * 977 * @param x first object to compare 978 * @param y second object to compare 979 * 980 * @return true if x is less than y 981 */ 982 public static boolean LT(final Object x, final Object y) { 983 final Object px = JSType.toPrimitive(x, Number.class); 984 final Object py = JSType.toPrimitive(y, Number.class); 985 986 return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : 987 JSType.toNumber(px) < JSType.toNumber(py); 988 } 989 990 private static boolean areBothString(final Object x, final Object y) { 991 return isString(x) && isString(y); 992 } 993 994 /** 995 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 996 * 997 * @param x first object to compare 998 * @param y second object to compare 999 * 1000 * @return true if x is greater than y 1001 */ 1002 public static boolean GT(final Object x, final Object y) { 1003 final Object px = JSType.toPrimitive(x, Number.class); 1004 final Object py = JSType.toPrimitive(y, Number.class); 1005 1006 return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : 1007 JSType.toNumber(px) > JSType.toNumber(py); 1008 } 1009 1010 /** 1011 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation 1012 * 1013 * @param x first object to compare 1014 * @param y second object to compare 1015 * 1016 * @return true if x is less than or equal to y 1017 */ 1018 public static boolean LE(final Object x, final Object y) { 1019 final Object px = JSType.toPrimitive(x, Number.class); 1020 final Object py = JSType.toPrimitive(y, Number.class); 1021 1022 return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : 1023 JSType.toNumber(px) <= JSType.toNumber(py); 1024 } 1025 1026 /** 1027 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 1028 * 1029 * @param x first object to compare 1030 * @param y second object to compare 1031 * 1032 * @return true if x is greater than or equal to y 1033 */ 1034 public static boolean GE(final Object x, final Object y) { 1035 final Object px = JSType.toPrimitive(x, Number.class); 1036 final Object py = JSType.toPrimitive(y, Number.class); 1037 1038 return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : 1039 JSType.toNumber(px) >= JSType.toNumber(py); 1040 } 1041 1042 /** 1043 * Tag a reserved name as invalidated - used when someone writes 1044 * to a property with this name - overly conservative, but link time 1045 * is too late to apply e.g. apply->call specialization 1046 * @param name property name 1047 */ 1048 public static void invalidateReservedBuiltinName(final String name) { 1049 final Context context = Context.getContextTrusted(); 1050 final SwitchPoint sp = context.getBuiltinSwitchPoint(name); 1051 assert sp != null; 1052 context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); 1053 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 1054 } 1055 1056 /** 1057 * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral). 1058 * 1059 * @param rawStrings array of template raw values 1060 * @param cookedStrings array of template values 1061 * @return template object 1062 */ 1063 public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) { 1064 final ScriptObject template = (ScriptObject)cookedStrings; 1065 final ScriptObject rawObj = (ScriptObject)rawStrings; 1066 assert rawObj.getArray().length() == template.getArray().length(); 1067 template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze()); 1068 template.freeze(); 1069 return template; 1070 } 1071 }