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