ScriptRuntime.java revision 1682:fb8b5b560a57
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 // value Iterator for important Java objects - arrays, maps, iterables. 317 private static Iterator<?> iteratorForJavaArrayOrList(final Object obj) { 318 if (obj != null && obj.getClass().isArray()) { 319 final Object array = obj; 320 final int length = Array.getLength(obj); 321 322 return new Iterator<Object>() { 323 private int index = 0; 324 325 @Override 326 public boolean hasNext() { 327 return index < length; 328 } 329 330 @Override 331 public Object next() { 332 if (index >= length) { 333 throw new NoSuchElementException(); 334 } 335 return Array.get(array, index++); 336 } 337 338 @Override 339 public void remove() { 340 throw new UnsupportedOperationException("remove"); 341 } 342 }; 343 } 344 345 if (obj instanceof Iterable) { 346 return ((Iterable<?>)obj).iterator(); 347 } 348 349 return null; 350 } 351 352 /** 353 * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS 354 * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over 355 * map values. 356 * @param obj object to iterate on. 357 * @return iterator over the object's property values. 358 */ 359 public static Iterator<?> toValueIterator(final Object obj) { 360 if (obj instanceof ScriptObject) { 361 return ((ScriptObject)obj).valueIterator(); 362 } 363 364 if (obj instanceof JSObject) { 365 return ((JSObject)obj).values().iterator(); 366 } 367 368 final Iterator<?> itr = iteratorForJavaArrayOrList(obj); 369 if (itr != null) { 370 return itr; 371 } 372 373 if (obj instanceof Map) { 374 return ((Map<?,?>)obj).values().iterator(); 375 } 376 377 final Object wrapped = Global.instance().wrapAsObject(obj); 378 if (wrapped instanceof ScriptObject) { 379 return ((ScriptObject)wrapped).valueIterator(); 380 } 381 382 return Collections.emptyIterator(); 383 } 384 385 /** 386 * Returns an iterator over property values used in the {@code for ... of} statement. The iterator uses the 387 * Iterator interface defined in version 6 of the ECMAScript specification. 388 * 389 * @param obj object to iterate on. 390 * @return iterator based on the ECMA 6 Iterator interface. 391 */ 392 public static Iterator<?> toES6Iterator(final Object obj) { 393 // if not a ScriptObject, try convenience iterator for Java objects! 394 if (!(obj instanceof ScriptObject)) { 395 final Iterator<?> itr = iteratorForJavaArrayOrList(obj); 396 if (itr != null) { 397 return itr; 398 } 399 } 400 401 final Global global = Global.instance(); 402 final Object iterator = AbstractIterator.getIterator(Global.toObject(obj), global); 403 404 final InvokeByName nextInvoker = AbstractIterator.getNextInvoker(global); 405 final MethodHandle doneInvoker = AbstractIterator.getDoneInvoker(global); 406 final MethodHandle valueInvoker = AbstractIterator.getValueInvoker(global); 407 408 return new Iterator<Object>() { 409 410 private Object nextResult = nextResult(); 411 412 private Object nextResult() { 413 try { 414 final Object next = nextInvoker.getGetter().invokeExact(iterator); 415 if (Bootstrap.isCallable(next)) { 416 return nextInvoker.getInvoker().invokeExact(next, iterator, (Object) null); 417 } 418 } catch (final RuntimeException|Error r) { 419 throw r; 420 } catch (final Throwable t) { 421 throw new RuntimeException(t); 422 } 423 return null; 424 } 425 426 @Override 427 public boolean hasNext() { 428 if (nextResult == null) { 429 return false; 430 } 431 try { 432 final Object done = doneInvoker.invokeExact(nextResult); 433 return !JSType.toBoolean(done); 434 } catch (final RuntimeException|Error r) { 435 throw r; 436 } catch (final Throwable t) { 437 throw new RuntimeException(t); 438 } 439 } 440 441 @Override 442 public Object next() { 443 if (nextResult == null) { 444 return Undefined.getUndefined(); 445 } 446 try { 447 final Object result = nextResult; 448 nextResult = nextResult(); 449 return valueInvoker.invokeExact(result); 450 } catch (final RuntimeException|Error r) { 451 throw r; 452 } catch (final Throwable t) { 453 throw new RuntimeException(t); 454 } 455 } 456 457 @Override 458 public void remove() { 459 throw new UnsupportedOperationException("remove"); 460 } 461 }; 462 } 463 464 /** 465 * Merge a scope into its prototype's map. 466 * Merge a scope into its prototype. 467 * 468 * @param scope Scope to merge. 469 * @return prototype object after merge 470 */ 471 public static ScriptObject mergeScope(final ScriptObject scope) { 472 final ScriptObject parentScope = scope.getProto(); 473 parentScope.addBoundProperties(scope); 474 return parentScope; 475 } 476 477 /** 478 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 479 * better performance by creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)} 480 * then using its {@link MethodHandle#invokeExact(Object...)} method instead. 481 * 482 * @param target ScriptFunction object. 483 * @param self Receiver in call. 484 * @param args Call arguments. 485 * @return Call result. 486 */ 487 public static Object apply(final ScriptFunction target, final Object self, final Object... args) { 488 try { 489 return target.invoke(self, args); 490 } catch (final RuntimeException | Error e) { 491 throw e; 492 } catch (final Throwable t) { 493 throw new RuntimeException(t); 494 } 495 } 496 497 /** 498 * Throws a reference error for an undefined variable. 499 * 500 * @param name the variable name 501 */ 502 public static void throwReferenceError(final String name) { 503 throw referenceError("not.defined", name); 504 } 505 506 /** 507 * Throws a type error for an assignment to a const. 508 * 509 * @param name the const name 510 */ 511 public static void throwConstTypeError(final String name) { 512 throw typeError("assign.constant", name); 513 } 514 515 /** 516 * Call a script function as a constructor with given args. 517 * 518 * @param target ScriptFunction object. 519 * @param args Call arguments. 520 * @return Constructor call result. 521 */ 522 public static Object construct(final ScriptFunction target, final Object... args) { 523 try { 524 return target.construct(args); 525 } catch (final RuntimeException | Error e) { 526 throw e; 527 } catch (final Throwable t) { 528 throw new RuntimeException(t); 529 } 530 } 531 532 /** 533 * Generic implementation of ECMA 9.12 - SameValue algorithm 534 * 535 * @param x first value to compare 536 * @param y second value to compare 537 * 538 * @return true if both objects have the same value 539 */ 540 public static boolean sameValue(final Object x, final Object y) { 541 final JSType xType = JSType.ofNoFunction(x); 542 final JSType yType = JSType.ofNoFunction(y); 543 544 if (xType != yType) { 545 return false; 546 } 547 548 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 549 return true; 550 } 551 552 if (xType == JSType.NUMBER) { 553 final double xVal = ((Number)x).doubleValue(); 554 final double yVal = ((Number)y).doubleValue(); 555 556 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 557 return true; 558 } 559 560 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 561 if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) { 562 return false; 563 } 564 565 return xVal == yVal; 566 } 567 568 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 569 return x.equals(y); 570 } 571 572 return x == y; 573 } 574 575 /** 576 * Returns AST as JSON compatible string. This is used to 577 * implement "parse" function in resources/parse.js script. 578 * 579 * @param code code to be parsed 580 * @param name name of the code source (used for location) 581 * @param includeLoc tells whether to include location information for nodes or not 582 * @return JSON string representation of AST of the supplied code 583 */ 584 public static String parse(final String code, final String name, final boolean includeLoc) { 585 return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc); 586 } 587 588 /** 589 * Test whether a char is valid JavaScript whitespace 590 * @param ch a char 591 * @return true if valid JavaScript whitespace 592 */ 593 public static boolean isJSWhitespace(final char ch) { 594 return Lexer.isJSWhitespace(ch); 595 } 596 597 /** 598 * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement, 599 * use {@link ScriptObject#getProto()} on the scope. 600 * 601 * @param scope existing scope 602 * @param expression expression in with 603 * 604 * @return {@link WithObject} that is the new scope 605 */ 606 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 607 final Global global = Context.getGlobal(); 608 if (expression == UNDEFINED) { 609 throw typeError(global, "cant.apply.with.to.undefined"); 610 } else if (expression == null) { 611 throw typeError(global, "cant.apply.with.to.null"); 612 } 613 614 if (expression instanceof ScriptObjectMirror) { 615 final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); 616 if (unwrapped instanceof ScriptObject) { 617 return new WithObject(scope, (ScriptObject)unwrapped); 618 } 619 // foreign ScriptObjectMirror 620 final ScriptObject exprObj = global.newObject(); 621 NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); 622 return new WithObject(scope, exprObj); 623 } 624 625 final Object wrappedExpr = JSType.toScriptObject(global, expression); 626 if (wrappedExpr instanceof ScriptObject) { 627 return new WithObject(scope, (ScriptObject)wrappedExpr); 628 } 629 630 throw typeError(global, "cant.apply.with.to.non.scriptobject"); 631 } 632 633 /** 634 * ECMA 11.6.1 - The addition operator (+) - generic implementation 635 * 636 * @param x first term 637 * @param y second term 638 * 639 * @return result of addition 640 */ 641 public static Object ADD(final Object x, final Object y) { 642 // This prefix code to handle Number special is for optimization. 643 final boolean xIsNumber = x instanceof Number; 644 final boolean yIsNumber = y instanceof Number; 645 646 if (xIsNumber && yIsNumber) { 647 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 648 } 649 650 final boolean xIsUndefined = x == UNDEFINED; 651 final boolean yIsUndefined = y == UNDEFINED; 652 653 if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { 654 return Double.NaN; 655 } 656 657 // code below is as per the spec. 658 final Object xPrim = JSType.toPrimitive(x); 659 final Object yPrim = JSType.toPrimitive(y); 660 661 if (isString(xPrim) || isString(yPrim)) { 662 try { 663 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 664 } catch (final IllegalArgumentException iae) { 665 throw rangeError(iae, "concat.string.too.big"); 666 } 667 } 668 669 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 670 } 671 672 /** 673 * Debugger hook. 674 * TODO: currently unimplemented 675 * 676 * @return undefined 677 */ 678 public static Object DEBUGGER() { 679 return UNDEFINED; 680 } 681 682 /** 683 * New hook 684 * 685 * @param clazz type for the clss 686 * @param args constructor arguments 687 * 688 * @return undefined 689 */ 690 public static Object NEW(final Object clazz, final Object... args) { 691 return UNDEFINED; 692 } 693 694 /** 695 * ECMA 11.4.3 The typeof Operator - generic implementation 696 * 697 * @param object the object from which to retrieve property to type check 698 * @param property property in object to check 699 * 700 * @return type name 701 */ 702 public static Object TYPEOF(final Object object, final Object property) { 703 Object obj = object; 704 705 if (property != null) { 706 if (obj instanceof ScriptObject) { 707 obj = ((ScriptObject)obj).get(property); 708 if(Global.isLocationPropertyPlaceholder(obj)) { 709 if(CompilerConstants.__LINE__.name().equals(property)) { 710 obj = 0; 711 } else { 712 obj = ""; 713 } 714 } 715 } else if (object instanceof Undefined) { 716 obj = ((Undefined)obj).get(property); 717 } else if (object == null) { 718 throw typeError("cant.get.property", safeToString(property), "null"); 719 } else if (JSType.isPrimitive(obj)) { 720 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 721 } else if (obj instanceof JSObject) { 722 obj = ((JSObject)obj).getMember(property.toString()); 723 } else { 724 obj = UNDEFINED; 725 } 726 } 727 728 return JSType.of(obj).typeName(); 729 } 730 731 /** 732 * Throw ReferenceError when LHS of assignment or increment/decrement 733 * operator is not an assignable node (say a literal) 734 * 735 * @param lhs Evaluated LHS 736 * @param rhs Evaluated RHS 737 * @param msg Additional LHS info for error message 738 * @return undefined 739 */ 740 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 741 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 742 } 743 744 /** 745 * ECMA 11.4.1 - delete operation, generic implementation 746 * 747 * @param obj object with property to delete 748 * @param property property to delete 749 * @param strict are we in strict mode 750 * 751 * @return true if property was successfully found and deleted 752 */ 753 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 754 if (obj instanceof ScriptObject) { 755 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 756 } 757 758 if (obj instanceof Undefined) { 759 return ((Undefined)obj).delete(property, false); 760 } 761 762 if (obj == null) { 763 throw typeError("cant.delete.property", safeToString(property), "null"); 764 } 765 766 if (obj instanceof ScriptObjectMirror) { 767 return ((ScriptObjectMirror)obj).delete(property); 768 } 769 770 if (JSType.isPrimitive(obj)) { 771 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 772 } 773 774 if (obj instanceof JSObject) { 775 ((JSObject)obj).removeMember(Objects.toString(property)); 776 return true; 777 } 778 779 // if object is not reference type, vacuously delete is successful. 780 return true; 781 } 782 783 /** 784 * ECMA 11.4.1 - delete operator, implementation for slow scopes 785 * 786 * This implementation of 'delete' walks the scope chain to find the scope that contains the 787 * property to be deleted, then invokes delete on it. 788 * 789 * @param obj top scope object 790 * @param property property to delete 791 * @param strict are we in strict mode 792 * 793 * @return true if property was successfully found and deleted 794 */ 795 public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) { 796 if (obj instanceof ScriptObject) { 797 ScriptObject sobj = (ScriptObject) obj; 798 final String key = property.toString(); 799 while (sobj != null && sobj.isScope()) { 800 final FindProperty find = sobj.findProperty(key, false); 801 if (find != null) { 802 return sobj.delete(key, Boolean.TRUE.equals(strict)); 803 } 804 sobj = sobj.getProto(); 805 } 806 } 807 return DELETE(obj, property, strict); 808 } 809 810 /** 811 * ECMA 11.4.1 - delete operator, special case 812 * 813 * This is 'delete' that always fails. We have to check strict mode and throw error. 814 * That is why this is a runtime function. Or else we could have inlined 'false'. 815 * 816 * @param property property to delete 817 * @param strict are we in strict mode 818 * 819 * @return false always 820 */ 821 public static boolean FAIL_DELETE(final Object property, final Object strict) { 822 if (Boolean.TRUE.equals(strict)) { 823 throw syntaxError("strict.cant.delete", safeToString(property)); 824 } 825 return false; 826 } 827 828 /** 829 * ECMA 11.9.1 - The equals operator (==) - generic implementation 830 * 831 * @param x first object to compare 832 * @param y second object to compare 833 * 834 * @return true if type coerced versions of objects are equal 835 */ 836 public static boolean EQ(final Object x, final Object y) { 837 return equals(x, y); 838 } 839 840 /** 841 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 842 * 843 * @param x first object to compare 844 * @param y second object to compare 845 * 846 * @return true if type coerced versions of objects are not equal 847 */ 848 public static boolean NE(final Object x, final Object y) { 849 return !EQ(x, y); 850 } 851 852 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 853 private static boolean equals(final Object x, final Object y) { 854 // We want to keep this method small so we skip reference equality check for numbers 855 // as NaN should return false when compared to itself (JDK-8043608). 856 if (x == y && !(x instanceof Number)) { 857 return true; 858 } 859 if (x instanceof ScriptObject && y instanceof ScriptObject) { 860 return false; // x != y 861 } 862 if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) { 863 return ScriptObjectMirror.identical(x, y); 864 } 865 return equalValues(x, y); 866 } 867 868 /** 869 * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value 870 * comparison applies). 871 * @param x one value 872 * @param y another value 873 * @return true if they're equal according to 11.9.3 874 */ 875 private static boolean equalValues(final Object x, final Object y) { 876 final JSType xType = JSType.ofNoFunction(x); 877 final JSType yType = JSType.ofNoFunction(y); 878 879 if (xType == yType) { 880 return equalSameTypeValues(x, y, xType); 881 } 882 883 return equalDifferentTypeValues(x, y, xType, yType); 884 } 885 886 /** 887 * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares 888 * values belonging to the same JSType. 889 * @param x one value 890 * @param y another value 891 * @param type the common type for the values 892 * @return true if they're equal 893 */ 894 private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { 895 if (type == JSType.UNDEFINED || type == JSType.NULL) { 896 return true; 897 } 898 899 if (type == JSType.NUMBER) { 900 return ((Number)x).doubleValue() == ((Number)y).doubleValue(); 901 } 902 903 if (type == JSType.STRING) { 904 // String may be represented by ConsString 905 return x.toString().equals(y.toString()); 906 } 907 908 if (type == JSType.BOOLEAN) { 909 return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); 910 } 911 912 return x == y; 913 } 914 915 /** 916 * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. 917 * @param x one value 918 * @param y another value 919 * @param xType the type for the value x 920 * @param yType the type for the value y 921 * @return true if they're equal 922 */ 923 private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { 924 if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) { 925 return true; 926 } else if (isNumberAndString(xType, yType)) { 927 return equalNumberToString(x, y); 928 } else if (isNumberAndString(yType, xType)) { 929 // Can reverse order as both are primitives 930 return equalNumberToString(y, x); 931 } else if (xType == JSType.BOOLEAN) { 932 return equalBooleanToAny(x, y); 933 } else if (yType == JSType.BOOLEAN) { 934 // Can reverse order as y is primitive 935 return equalBooleanToAny(y, x); 936 } else if (isPrimitiveAndObject(xType, yType)) { 937 return equalWrappedPrimitiveToObject(x, y); 938 } else if (isPrimitiveAndObject(yType, xType)) { 939 // Can reverse order as y is primitive 940 return equalWrappedPrimitiveToObject(y, x); 941 } 942 943 return false; 944 } 945 946 private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) { 947 return xType == JSType.UNDEFINED && yType == JSType.NULL; 948 } 949 950 private static boolean isNumberAndString(final JSType xType, final JSType yType) { 951 return xType == JSType.NUMBER && yType == JSType.STRING; 952 } 953 954 private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) { 955 return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT; 956 } 957 958 private static boolean equalNumberToString(final Object num, final Object str) { 959 // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We 960 // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number 961 // comparison. 962 return ((Number)num).doubleValue() == JSType.toNumber(str.toString()); 963 } 964 965 private static boolean equalBooleanToAny(final Object bool, final Object any) { 966 return equals(JSType.toNumber((Boolean)bool), any); 967 } 968 969 private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) { 970 return equals(numOrStr, JSType.toPrimitive(any)); 971 } 972 973 /** 974 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 975 * 976 * @param x first object to compare 977 * @param y second object to compare 978 * 979 * @return true if objects are equal 980 */ 981 public static boolean EQ_STRICT(final Object x, final Object y) { 982 return strictEquals(x, y); 983 } 984 985 /** 986 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 987 * 988 * @param x first object to compare 989 * @param y second object to compare 990 * 991 * @return true if objects are not equal 992 */ 993 public static boolean NE_STRICT(final Object x, final Object y) { 994 return !EQ_STRICT(x, y); 995 } 996 997 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 998 private static boolean strictEquals(final Object x, final Object y) { 999 // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having 1000 // NaN value is not equal to itself by value even though it is referentially. 1001 1002 final JSType xType = JSType.ofNoFunction(x); 1003 final JSType yType = JSType.ofNoFunction(y); 1004 1005 if (xType != yType) { 1006 return false; 1007 } 1008 1009 return equalSameTypeValues(x, y, xType); 1010 } 1011 1012 /** 1013 * ECMA 11.8.6 - The in operator - generic implementation 1014 * 1015 * @param property property to check for 1016 * @param obj object in which to check for property 1017 * 1018 * @return true if objects are equal 1019 */ 1020 public static boolean IN(final Object property, final Object obj) { 1021 final JSType rvalType = JSType.ofNoFunction(obj); 1022 1023 if (rvalType == JSType.OBJECT) { 1024 if (obj instanceof ScriptObject) { 1025 return ((ScriptObject)obj).has(property); 1026 } 1027 1028 if (obj instanceof JSObject) { 1029 return ((JSObject)obj).hasMember(Objects.toString(property)); 1030 } 1031 1032 return false; 1033 } 1034 1035 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 1036 } 1037 1038 /** 1039 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 1040 * 1041 * @param obj first object to compare 1042 * @param clazz type to check against 1043 * 1044 * @return true if {@code obj} is an instanceof {@code clazz} 1045 */ 1046 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 1047 if (clazz instanceof ScriptFunction) { 1048 if (obj instanceof ScriptObject) { 1049 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 1050 } 1051 return false; 1052 } 1053 1054 if (clazz instanceof StaticClass) { 1055 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 1056 } 1057 1058 if (clazz instanceof JSObject) { 1059 return ((JSObject)clazz).isInstance(obj); 1060 } 1061 1062 // provide for reverse hook 1063 if (obj instanceof JSObject) { 1064 return ((JSObject)obj).isInstanceOf(clazz); 1065 } 1066 1067 throw typeError("instanceof.on.non.object"); 1068 } 1069 1070 /** 1071 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 1072 * 1073 * @param x first object to compare 1074 * @param y second object to compare 1075 * 1076 * @return true if x is less than y 1077 */ 1078 public static boolean LT(final Object x, final Object y) { 1079 final Object px = JSType.toPrimitive(x, Number.class); 1080 final Object py = JSType.toPrimitive(y, Number.class); 1081 1082 return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : 1083 JSType.toNumber(px) < JSType.toNumber(py); 1084 } 1085 1086 private static boolean areBothString(final Object x, final Object y) { 1087 return isString(x) && isString(y); 1088 } 1089 1090 /** 1091 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 1092 * 1093 * @param x first object to compare 1094 * @param y second object to compare 1095 * 1096 * @return true if x is greater than y 1097 */ 1098 public static boolean GT(final Object x, final Object y) { 1099 final Object px = JSType.toPrimitive(x, Number.class); 1100 final Object py = JSType.toPrimitive(y, Number.class); 1101 1102 return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : 1103 JSType.toNumber(px) > JSType.toNumber(py); 1104 } 1105 1106 /** 1107 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation 1108 * 1109 * @param x first object to compare 1110 * @param y second object to compare 1111 * 1112 * @return true if x is less than or equal to y 1113 */ 1114 public static boolean LE(final Object x, final Object y) { 1115 final Object px = JSType.toPrimitive(x, Number.class); 1116 final Object py = JSType.toPrimitive(y, Number.class); 1117 1118 return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : 1119 JSType.toNumber(px) <= JSType.toNumber(py); 1120 } 1121 1122 /** 1123 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 1124 * 1125 * @param x first object to compare 1126 * @param y second object to compare 1127 * 1128 * @return true if x is greater than or equal to y 1129 */ 1130 public static boolean GE(final Object x, final Object y) { 1131 final Object px = JSType.toPrimitive(x, Number.class); 1132 final Object py = JSType.toPrimitive(y, Number.class); 1133 1134 return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : 1135 JSType.toNumber(px) >= JSType.toNumber(py); 1136 } 1137 1138 /** 1139 * Tag a reserved name as invalidated - used when someone writes 1140 * to a property with this name - overly conservative, but link time 1141 * is too late to apply e.g. apply->call specialization 1142 * @param name property name 1143 */ 1144 public static void invalidateReservedBuiltinName(final String name) { 1145 final Context context = Context.getContextTrusted(); 1146 final SwitchPoint sp = context.getBuiltinSwitchPoint(name); 1147 assert sp != null; 1148 context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); 1149 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 1150 } 1151 1152 /** 1153 * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral). 1154 * 1155 * @param rawStrings array of template raw values 1156 * @param cookedStrings array of template values 1157 * @return template object 1158 */ 1159 public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) { 1160 final ScriptObject template = (ScriptObject)cookedStrings; 1161 final ScriptObject rawObj = (ScriptObject)rawStrings; 1162 assert rawObj.getArray().length() == template.getArray().length(); 1163 template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze()); 1164 template.freeze(); 1165 return template; 1166 } 1167} 1168