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