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