ScriptRuntime.java revision 1513:2cebe18ffc70
1171169Smlaier/* 2171169Smlaier * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3171169Smlaier * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4171169Smlaier * 5171169Smlaier * This code is free software; you can redistribute it and/or modify it 6171169Smlaier * under the terms of the GNU General Public License version 2 only, as 7171169Smlaier * published by the Free Software Foundation. Oracle designates this 8171169Smlaier * particular file as subject to the "Classpath" exception as provided 9171169Smlaier * by Oracle in the LICENSE file that accompanied this code. 10171169Smlaier * 11171169Smlaier * This code is distributed in the hope that it will be useful, but WITHOUT 12171169Smlaier * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13171169Smlaier * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14171169Smlaier * version 2 for more details (a copy is included in the LICENSE file that 15171169Smlaier * accompanied this code). 16171169Smlaier * 17171169Smlaier * You should have received a copy of the GNU General Public License version 18171169Smlaier * 2 along with this work; if not, write to the Free Software Foundation, 19171169Smlaier * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20171169Smlaier * 21171169Smlaier * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22171169Smlaier * or visit www.oracle.com if you need additional information or have any 23171169Smlaier * questions. 24171169Smlaier */ 25171169Smlaier 26171169Smlaierpackage jdk.nashorn.internal.runtime; 27171169Smlaier 28171169Smlaierimport static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; 29171169Smlaierimport static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 30171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 31171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 32171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; 33171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 34171169Smlaierimport static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 35171169Smlaierimport static jdk.nashorn.internal.runtime.JSType.isString; 36171169Smlaier 37171169Smlaierimport java.lang.invoke.MethodHandle; 38171169Smlaierimport java.lang.invoke.MethodHandles; 39171169Smlaierimport java.lang.invoke.SwitchPoint; 40171169Smlaierimport java.lang.reflect.Array; 41171169Smlaierimport java.util.Collections; 42171169Smlaierimport java.util.Iterator; 43171169Smlaierimport java.util.List; 44171169Smlaierimport java.util.Locale; 45171169Smlaierimport java.util.Map; 46171169Smlaierimport java.util.NoSuchElementException; 47171169Smlaierimport java.util.Objects; 48171169Smlaier 49171169Smlaierimport jdk.internal.dynalink.beans.StaticClass; 50171169Smlaierimport jdk.nashorn.api.scripting.JSObject; 51171169Smlaierimport jdk.nashorn.api.scripting.ScriptObjectMirror; 52171169Smlaierimport jdk.nashorn.internal.codegen.ApplySpecialization; 53171169Smlaierimport jdk.nashorn.internal.codegen.CompilerConstants; 54171169Smlaierimport jdk.nashorn.internal.codegen.CompilerConstants.Call; 55171169Smlaierimport jdk.nashorn.internal.ir.debug.JSONWriter; 56171169Smlaierimport jdk.nashorn.internal.objects.Global; 57171169Smlaierimport jdk.nashorn.internal.objects.NativeObject; 58171169Smlaierimport jdk.nashorn.internal.parser.Lexer; 59171169Smlaierimport jdk.nashorn.internal.runtime.linker.Bootstrap; 60171169Smlaier 61171169Smlaier/** 62171169Smlaier * Utilities to be called by JavaScript runtime API and generated classes. 63171169Smlaier */ 64171169Smlaier 65171169Smlaierpublic final class ScriptRuntime { 66171169Smlaier private ScriptRuntime() { 67171169Smlaier } 68171169Smlaier 69171169Smlaier /** Singleton representing the empty array object '[]' */ 70171169Smlaier public static final Object[] EMPTY_ARRAY = new Object[0]; 71171169Smlaier 72171169Smlaier /** Unique instance of undefined. */ 73171169Smlaier public static final Undefined UNDEFINED = Undefined.getUndefined(); 74171169Smlaier 75171169Smlaier /** 76171169Smlaier * Unique instance of undefined used to mark empty array slots. 77171169Smlaier * Can't escape the array. 78171169Smlaier */ 79171169Smlaier public static final Undefined EMPTY = Undefined.getEmpty(); 80171169Smlaier 81171169Smlaier /** Method handle to generic + operator, operating on objects */ 82171169Smlaier public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class); 83171169Smlaier 84171169Smlaier /** Method handle to generic === operator, operating on objects */ 85171169Smlaier public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class); 86171169Smlaier 87171169Smlaier /** Method handle used to enter a {@code with} scope at runtime. */ 88171169Smlaier public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class); 89171169Smlaier 90171169Smlaier /** 91171169Smlaier * Method used to place a scope's variable into the Global scope, which has to be done for the 92171169Smlaier * properties declared at outermost script level. 93171169Smlaier */ 94171169Smlaier public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class); 95171169Smlaier 96171169Smlaier /** 97171169Smlaier * Return an appropriate iterator for the elements in a for-in construct 98171169Smlaier */ 99171169Smlaier public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class); 100171169Smlaier 101171169Smlaier /** 102171169Smlaier * Return an appropriate iterator for the elements in a for-each construct 103171169Smlaier */ 104171169Smlaier public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class); 105171169Smlaier 106171169Smlaier /** 107171169Smlaier * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to 108171169Smlaier * call sites that are known to be megamorphic. Using an invoke dynamic here would 109171169Smlaier * lead to the JVM deoptimizing itself to death 110171169Smlaier */ 111171169Smlaier public static final Call APPLY = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class); 112171169Smlaier 113171169Smlaier /** 114171169Smlaier * Throws a reference error for an undefined variable. 115171169Smlaier */ 116171169Smlaier public static final Call THROW_REFERENCE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwReferenceError", void.class, String.class); 117171169Smlaier 118171169Smlaier /** 119171169Smlaier * Throws a reference error for an undefined variable. 120171169Smlaier */ 121171169Smlaier public static final Call THROW_CONST_TYPE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwConstTypeError", void.class, String.class); 122171169Smlaier 123171169Smlaier /** 124171169Smlaier * Used to invalidate builtin names, e.g "Function" mapping to all properties in Function.prototype and Function.prototype itself. 125171169Smlaier */ 126171169Smlaier public static final Call INVALIDATE_RESERVED_BUILTIN_NAME = staticCallNoLookup(ScriptRuntime.class, "invalidateReservedBuiltinName", void.class, String.class); 127171169Smlaier 128171169Smlaier /** 129171169Smlaier * Converts a switch tag value to a simple integer. deflt value if it can't. 130171169Smlaier * 131171169Smlaier * @param tag Switch statement tag value. 132171169Smlaier * @param deflt default to use if not convertible. 133171169Smlaier * @return int tag value (or deflt.) 134171169Smlaier */ 135171169Smlaier public static int switchTagAsInt(final Object tag, final int deflt) { 136171169Smlaier if (tag instanceof Number) { 137171169Smlaier final double d = ((Number)tag).doubleValue(); 138171169Smlaier if (isRepresentableAsInt(d)) { 139171169Smlaier return (int)d; 140171169Smlaier } 141171169Smlaier } 142171169Smlaier return deflt; 143171169Smlaier } 144171169Smlaier 145171169Smlaier /** 146171169Smlaier * Converts a switch tag value to a simple integer. deflt value if it can't. 147171169Smlaier * 148171169Smlaier * @param tag Switch statement tag value. 149171169Smlaier * @param deflt default to use if not convertible. 150171169Smlaier * @return int tag value (or deflt.) 151171169Smlaier */ 152171169Smlaier public static int switchTagAsInt(final boolean tag, final int deflt) { 153171169Smlaier return deflt; 154171169Smlaier } 155171169Smlaier 156171169Smlaier /** 157171169Smlaier * Converts a switch tag value to a simple integer. deflt value if it can't. 158171169Smlaier * 159171169Smlaier * @param tag Switch statement tag value. 160171169Smlaier * @param deflt default to use if not convertible. 161171169Smlaier * @return int tag value (or deflt.) 162171169Smlaier */ 163171169Smlaier public static int switchTagAsInt(final long tag, final int deflt) { 164171169Smlaier return isRepresentableAsInt(tag) ? (int)tag : deflt; 165171169Smlaier } 166171169Smlaier 167171169Smlaier /** 168171169Smlaier * Converts a switch tag value to a simple integer. deflt value if it can't. 169171169Smlaier * 170171169Smlaier * @param tag Switch statement tag value. 171171169Smlaier * @param deflt default to use if not convertible. 172171169Smlaier * @return int tag value (or deflt.) 173171169Smlaier */ 174171169Smlaier public static int switchTagAsInt(final double tag, final int deflt) { 175171169Smlaier return isRepresentableAsInt(tag) ? (int)tag : deflt; 176171169Smlaier } 177171169Smlaier 178171169Smlaier /** 179171169Smlaier * This is the builtin implementation of {@code Object.prototype.toString} 180171169Smlaier * @param self reference 181171169Smlaier * @return string representation as object 182171169Smlaier */ 183171169Smlaier public static String builtinObjectToString(final Object self) { 184171169Smlaier String className; 185171169Smlaier // Spec tells us to convert primitives by ToObject.. 186171169Smlaier // But we don't need to -- all we need is the right class name 187171169Smlaier // of the corresponding primitive wrapper type. 188171169Smlaier 189171169Smlaier final JSType type = JSType.ofNoFunction(self); 190171169Smlaier 191171169Smlaier switch (type) { 192171169Smlaier case BOOLEAN: 193171169Smlaier className = "Boolean"; 194171169Smlaier break; 195171169Smlaier case NUMBER: 196171169Smlaier className = "Number"; 197171169Smlaier break; 198171169Smlaier case STRING: 199171169Smlaier className = "String"; 200171169Smlaier break; 201171169Smlaier // special case of null and undefined 202171169Smlaier case NULL: 203171169Smlaier className = "Null"; 204171169Smlaier break; 205171169Smlaier case UNDEFINED: 206171169Smlaier className = "Undefined"; 207171169Smlaier break; 208171169Smlaier case OBJECT: 209171169Smlaier if (self instanceof ScriptObject) { 210171169Smlaier className = ((ScriptObject)self).getClassName(); 211171169Smlaier } else if (self instanceof JSObject) { 212171169Smlaier className = ((JSObject)self).getClassName(); 213171169Smlaier } else { 214171169Smlaier className = self.getClass().getName(); 215171169Smlaier } 216171169Smlaier break; 217171169Smlaier default: 218171169Smlaier // Nashorn extension: use Java class name 219171169Smlaier className = self.getClass().getName(); 220 break; 221 } 222 223 final StringBuilder sb = new StringBuilder(); 224 sb.append("[object "); 225 sb.append(className); 226 sb.append(']'); 227 228 return sb.toString(); 229 } 230 231 /** 232 * This is called whenever runtime wants to throw an error and wants to provide 233 * meaningful information about an object. We don't want to call toString which 234 * ends up calling "toString" from script world which may itself throw error. 235 * When we want to throw an error, we don't additional error from script land 236 * -- which may sometimes lead to infinite recursion. 237 * 238 * @param obj Object to converted to String safely (without calling user script) 239 * @return safe String representation of the given object 240 */ 241 public static String safeToString(final Object obj) { 242 return JSType.toStringImpl(obj, true); 243 } 244 245 /** 246 * Returns an iterator over property identifiers used in the {@code for...in} statement. Note that the ECMAScript 247 * 5.1 specification, chapter 12.6.4. uses the terminology "property names", which seems to imply that the property 248 * identifiers are expected to be strings, but this is not actually spelled out anywhere, and Nashorn will in some 249 * cases deviate from this. Namely, we guarantee to always return an iterator over {@link String} values for any 250 * built-in JavaScript object. We will however return an iterator over {@link Integer} objects for native Java 251 * arrays and {@link List} objects, as well as arbitrary objects representing keys of a {@link Map}. Therefore, the 252 * expression {@code typeof i} within a {@code for(i in obj)} statement can return something other than 253 * {@code string} when iterating over native Java arrays, {@code List}, and {@code Map} objects. 254 * @param obj object to iterate on. 255 * @return iterator over the object's property names. 256 */ 257 public static Iterator<?> toPropertyIterator(final Object obj) { 258 if (obj instanceof ScriptObject) { 259 return ((ScriptObject)obj).propertyIterator(); 260 } 261 262 if (obj != null && obj.getClass().isArray()) { 263 return new RangeIterator(Array.getLength(obj)); 264 } 265 266 if (obj instanceof JSObject) { 267 return ((JSObject)obj).keySet().iterator(); 268 } 269 270 if (obj instanceof List) { 271 return new RangeIterator(((List<?>)obj).size()); 272 } 273 274 if (obj instanceof Map) { 275 return ((Map<?,?>)obj).keySet().iterator(); 276 } 277 278 final Object wrapped = Global.instance().wrapAsObject(obj); 279 if (wrapped instanceof ScriptObject) { 280 return ((ScriptObject)wrapped).propertyIterator(); 281 } 282 283 return Collections.emptyIterator(); 284 } 285 286 private static final class RangeIterator implements Iterator<Integer> { 287 private final int length; 288 private int index; 289 290 RangeIterator(final int length) { 291 this.length = length; 292 } 293 294 @Override 295 public boolean hasNext() { 296 return index < length; 297 } 298 299 @Override 300 public Integer next() { 301 return index++; 302 } 303 304 @Override 305 public void remove() { 306 throw new UnsupportedOperationException("remove"); 307 } 308 } 309 310 /** 311 * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS 312 * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over 313 * map values. 314 * @param obj object to iterate on. 315 * @return iterator over the object's property values. 316 */ 317 public static Iterator<?> toValueIterator(final Object obj) { 318 if (obj instanceof ScriptObject) { 319 return ((ScriptObject)obj).valueIterator(); 320 } 321 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 JSObject) { 350 return ((JSObject)obj).values().iterator(); 351 } 352 353 if (obj instanceof Map) { 354 return ((Map<?,?>)obj).values().iterator(); 355 } 356 357 if (obj instanceof Iterable) { 358 return ((Iterable<?>)obj).iterator(); 359 } 360 361 final Object wrapped = Global.instance().wrapAsObject(obj); 362 if (wrapped instanceof ScriptObject) { 363 return ((ScriptObject)wrapped).valueIterator(); 364 } 365 366 return Collections.emptyIterator(); 367 } 368 369 /** 370 * Merge a scope into its prototype's map. 371 * Merge a scope into its prototype. 372 * 373 * @param scope Scope to merge. 374 * @return prototype object after merge 375 */ 376 public static ScriptObject mergeScope(final ScriptObject scope) { 377 final ScriptObject parentScope = scope.getProto(); 378 parentScope.addBoundProperties(scope); 379 return parentScope; 380 } 381 382 /** 383 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 384 * better performance by creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)} 385 * then using its {@link MethodHandle#invokeExact(Object...)} method instead. 386 * 387 * @param target ScriptFunction object. 388 * @param self Receiver in call. 389 * @param args Call arguments. 390 * @return Call result. 391 */ 392 public static Object apply(final ScriptFunction target, final Object self, final Object... args) { 393 try { 394 return target.invoke(self, args); 395 } catch (final RuntimeException | Error e) { 396 throw e; 397 } catch (final Throwable t) { 398 throw new RuntimeException(t); 399 } 400 } 401 402 /** 403 * Throws a reference error for an undefined variable. 404 * 405 * @param name the variable name 406 */ 407 public static void throwReferenceError(final String name) { 408 throw referenceError("not.defined", name); 409 } 410 411 /** 412 * Throws a type error for an assignment to a const. 413 * 414 * @param name the const name 415 */ 416 public static void throwConstTypeError(final String name) { 417 throw typeError("assign.constant", name); 418 } 419 420 /** 421 * Call a script function as a constructor with given args. 422 * 423 * @param target ScriptFunction object. 424 * @param args Call arguments. 425 * @return Constructor call result. 426 */ 427 public static Object construct(final ScriptFunction target, final Object... args) { 428 try { 429 return target.construct(args); 430 } catch (final RuntimeException | Error e) { 431 throw e; 432 } catch (final Throwable t) { 433 throw new RuntimeException(t); 434 } 435 } 436 437 /** 438 * Generic implementation of ECMA 9.12 - SameValue algorithm 439 * 440 * @param x first value to compare 441 * @param y second value to compare 442 * 443 * @return true if both objects have the same value 444 */ 445 public static boolean sameValue(final Object x, final Object y) { 446 final JSType xType = JSType.ofNoFunction(x); 447 final JSType yType = JSType.ofNoFunction(y); 448 449 if (xType != yType) { 450 return false; 451 } 452 453 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 454 return true; 455 } 456 457 if (xType == JSType.NUMBER) { 458 final double xVal = ((Number)x).doubleValue(); 459 final double yVal = ((Number)y).doubleValue(); 460 461 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 462 return true; 463 } 464 465 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 466 if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) { 467 return false; 468 } 469 470 return xVal == yVal; 471 } 472 473 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 474 return x.equals(y); 475 } 476 477 return x == y; 478 } 479 480 /** 481 * Returns AST as JSON compatible string. This is used to 482 * implement "parse" function in resources/parse.js script. 483 * 484 * @param code code to be parsed 485 * @param name name of the code source (used for location) 486 * @param includeLoc tells whether to include location information for nodes or not 487 * @return JSON string representation of AST of the supplied code 488 */ 489 public static String parse(final String code, final String name, final boolean includeLoc) { 490 return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc); 491 } 492 493 /** 494 * Test whether a char is valid JavaScript whitespace 495 * @param ch a char 496 * @return true if valid JavaScript whitespace 497 */ 498 public static boolean isJSWhitespace(final char ch) { 499 return Lexer.isJSWhitespace(ch); 500 } 501 502 /** 503 * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement, 504 * use {@link ScriptObject#getProto()} on the scope. 505 * 506 * @param scope existing scope 507 * @param expression expression in with 508 * 509 * @return {@link WithObject} that is the new scope 510 */ 511 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 512 final Global global = Context.getGlobal(); 513 if (expression == UNDEFINED) { 514 throw typeError(global, "cant.apply.with.to.undefined"); 515 } else if (expression == null) { 516 throw typeError(global, "cant.apply.with.to.null"); 517 } 518 519 if (expression instanceof ScriptObjectMirror) { 520 final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); 521 if (unwrapped instanceof ScriptObject) { 522 return new WithObject(scope, (ScriptObject)unwrapped); 523 } 524 // foreign ScriptObjectMirror 525 final ScriptObject exprObj = global.newObject(); 526 NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); 527 return new WithObject(scope, exprObj); 528 } 529 530 final Object wrappedExpr = JSType.toScriptObject(global, expression); 531 if (wrappedExpr instanceof ScriptObject) { 532 return new WithObject(scope, (ScriptObject)wrappedExpr); 533 } 534 535 throw typeError(global, "cant.apply.with.to.non.scriptobject"); 536 } 537 538 /** 539 * ECMA 11.6.1 - The addition operator (+) - generic implementation 540 * 541 * @param x first term 542 * @param y second term 543 * 544 * @return result of addition 545 */ 546 public static Object ADD(final Object x, final Object y) { 547 // This prefix code to handle Number special is for optimization. 548 final boolean xIsNumber = x instanceof Number; 549 final boolean yIsNumber = y instanceof Number; 550 551 if (xIsNumber && yIsNumber) { 552 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 553 } 554 555 final boolean xIsUndefined = x == UNDEFINED; 556 final boolean yIsUndefined = y == UNDEFINED; 557 558 if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { 559 return Double.NaN; 560 } 561 562 // code below is as per the spec. 563 final Object xPrim = JSType.toPrimitive(x); 564 final Object yPrim = JSType.toPrimitive(y); 565 566 if (isString(xPrim) || isString(yPrim)) { 567 try { 568 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 569 } catch (final IllegalArgumentException iae) { 570 throw rangeError(iae, "concat.string.too.big"); 571 } 572 } 573 574 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 575 } 576 577 /** 578 * Debugger hook. 579 * TODO: currently unimplemented 580 * 581 * @return undefined 582 */ 583 public static Object DEBUGGER() { 584 return UNDEFINED; 585 } 586 587 /** 588 * New hook 589 * 590 * @param clazz type for the clss 591 * @param args constructor arguments 592 * 593 * @return undefined 594 */ 595 public static Object NEW(final Object clazz, final Object... args) { 596 return UNDEFINED; 597 } 598 599 /** 600 * ECMA 11.4.3 The typeof Operator - generic implementation 601 * 602 * @param object the object from which to retrieve property to type check 603 * @param property property in object to check 604 * 605 * @return type name 606 */ 607 public static Object TYPEOF(final Object object, final Object property) { 608 Object obj = object; 609 610 if (property != null) { 611 if (obj instanceof ScriptObject) { 612 obj = ((ScriptObject)obj).get(property); 613 if(Global.isLocationPropertyPlaceholder(obj)) { 614 if(CompilerConstants.__LINE__.name().equals(property)) { 615 obj = 0; 616 } else { 617 obj = ""; 618 } 619 } 620 } else if (object instanceof Undefined) { 621 obj = ((Undefined)obj).get(property); 622 } else if (object == null) { 623 throw typeError("cant.get.property", safeToString(property), "null"); 624 } else if (JSType.isPrimitive(obj)) { 625 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 626 } else if (obj instanceof JSObject) { 627 obj = ((JSObject)obj).getMember(property.toString()); 628 } else { 629 obj = UNDEFINED; 630 } 631 } 632 633 return JSType.of(obj).typeName(); 634 } 635 636 /** 637 * Throw ReferenceError when LHS of assignment or increment/decrement 638 * operator is not an assignable node (say a literal) 639 * 640 * @param lhs Evaluated LHS 641 * @param rhs Evaluated RHS 642 * @param msg Additional LHS info for error message 643 * @return undefined 644 */ 645 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 646 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 647 } 648 649 /** 650 * ECMA 11.4.1 - delete operation, generic implementation 651 * 652 * @param obj object with property to delete 653 * @param property property to delete 654 * @param strict are we in strict mode 655 * 656 * @return true if property was successfully found and deleted 657 */ 658 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 659 if (obj instanceof ScriptObject) { 660 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 661 } 662 663 if (obj instanceof Undefined) { 664 return ((Undefined)obj).delete(property, false); 665 } 666 667 if (obj == null) { 668 throw typeError("cant.delete.property", safeToString(property), "null"); 669 } 670 671 if (obj instanceof ScriptObjectMirror) { 672 return ((ScriptObjectMirror)obj).delete(property); 673 } 674 675 if (JSType.isPrimitive(obj)) { 676 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 677 } 678 679 if (obj instanceof JSObject) { 680 ((JSObject)obj).removeMember(Objects.toString(property)); 681 return true; 682 } 683 684 // if object is not reference type, vacuously delete is successful. 685 return true; 686 } 687 688 /** 689 * ECMA 11.4.1 - delete operator, implementation for slow scopes 690 * 691 * This implementation of 'delete' walks the scope chain to find the scope that contains the 692 * property to be deleted, then invokes delete on it. 693 * 694 * @param obj top scope object 695 * @param property property to delete 696 * @param strict are we in strict mode 697 * 698 * @return true if property was successfully found and deleted 699 */ 700 public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) { 701 if (obj instanceof ScriptObject) { 702 ScriptObject sobj = (ScriptObject) obj; 703 final String key = property.toString(); 704 while (sobj != null && sobj.isScope()) { 705 final FindProperty find = sobj.findProperty(key, false); 706 if (find != null) { 707 return sobj.delete(key, Boolean.TRUE.equals(strict)); 708 } 709 sobj = sobj.getProto(); 710 } 711 } 712 return DELETE(obj, property, strict); 713 } 714 715 /** 716 * ECMA 11.4.1 - delete operator, special case 717 * 718 * This is 'delete' that always fails. We have to check strict mode and throw error. 719 * That is why this is a runtime function. Or else we could have inlined 'false'. 720 * 721 * @param property property to delete 722 * @param strict are we in strict mode 723 * 724 * @return false always 725 */ 726 public static boolean FAIL_DELETE(final Object property, final Object strict) { 727 if (Boolean.TRUE.equals(strict)) { 728 throw syntaxError("strict.cant.delete", safeToString(property)); 729 } 730 return false; 731 } 732 733 /** 734 * ECMA 11.9.1 - The equals operator (==) - generic implementation 735 * 736 * @param x first object to compare 737 * @param y second object to compare 738 * 739 * @return true if type coerced versions of objects are equal 740 */ 741 public static boolean EQ(final Object x, final Object y) { 742 return equals(x, y); 743 } 744 745 /** 746 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 747 * 748 * @param x first object to compare 749 * @param y second object to compare 750 * 751 * @return true if type coerced versions of objects are not equal 752 */ 753 public static boolean NE(final Object x, final Object y) { 754 return !EQ(x, y); 755 } 756 757 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 758 private static boolean equals(final Object x, final Object y) { 759 if (x == y) { 760 return true; 761 } 762 if (x instanceof ScriptObject && y instanceof ScriptObject) { 763 return false; // x != y 764 } 765 if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) { 766 return ScriptObjectMirror.identical(x, y); 767 } 768 return equalValues(x, y); 769 } 770 771 /** 772 * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value 773 * comparison applies). 774 * @param x one value 775 * @param y another value 776 * @return true if they're equal according to 11.9.3 777 */ 778 private static boolean equalValues(final Object x, final Object y) { 779 final JSType xType = JSType.ofNoFunction(x); 780 final JSType yType = JSType.ofNoFunction(y); 781 782 if (xType == yType) { 783 return equalSameTypeValues(x, y, xType); 784 } 785 786 return equalDifferentTypeValues(x, y, xType, yType); 787 } 788 789 /** 790 * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares 791 * values belonging to the same JSType. 792 * @param x one value 793 * @param y another value 794 * @param type the common type for the values 795 * @return true if they're equal 796 */ 797 private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { 798 if (type == JSType.UNDEFINED || type == JSType.NULL) { 799 return true; 800 } 801 802 if (type == JSType.NUMBER) { 803 return ((Number)x).doubleValue() == ((Number)y).doubleValue(); 804 } 805 806 if (type == JSType.STRING) { 807 // String may be represented by ConsString 808 return x.toString().equals(y.toString()); 809 } 810 811 if (type == JSType.BOOLEAN) { 812 return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); 813 } 814 815 return x == y; 816 } 817 818 /** 819 * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. 820 * @param x one value 821 * @param y another value 822 * @param xType the type for the value x 823 * @param yType the type for the value y 824 * @return true if they're equal 825 */ 826 private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { 827 if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) { 828 return true; 829 } else if (isNumberAndString(xType, yType)) { 830 return equalNumberToString(x, y); 831 } else if (isNumberAndString(yType, xType)) { 832 // Can reverse order as both are primitives 833 return equalNumberToString(y, x); 834 } else if (xType == JSType.BOOLEAN) { 835 return equalBooleanToAny(x, y); 836 } else if (yType == JSType.BOOLEAN) { 837 // Can reverse order as y is primitive 838 return equalBooleanToAny(y, x); 839 } else if (isPrimitiveAndObject(xType, yType)) { 840 return equalWrappedPrimitiveToObject(x, y); 841 } else if (isPrimitiveAndObject(yType, xType)) { 842 // Can reverse order as y is primitive 843 return equalWrappedPrimitiveToObject(y, x); 844 } 845 846 return false; 847 } 848 849 private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) { 850 return xType == JSType.UNDEFINED && yType == JSType.NULL; 851 } 852 853 private static boolean isNumberAndString(final JSType xType, final JSType yType) { 854 return xType == JSType.NUMBER && yType == JSType.STRING; 855 } 856 857 private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) { 858 return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT; 859 } 860 861 private static boolean equalNumberToString(final Object num, final Object str) { 862 // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We 863 // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number 864 // comparison. 865 return ((Number)num).doubleValue() == JSType.toNumber(str.toString()); 866 } 867 868 private static boolean equalBooleanToAny(final Object bool, final Object any) { 869 return equals(JSType.toNumber((Boolean)bool), any); 870 } 871 872 private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) { 873 return equals(numOrStr, JSType.toPrimitive(any)); 874 } 875 876 /** 877 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 878 * 879 * @param x first object to compare 880 * @param y second object to compare 881 * 882 * @return true if objects are equal 883 */ 884 public static boolean EQ_STRICT(final Object x, final Object y) { 885 return strictEquals(x, y); 886 } 887 888 /** 889 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 890 * 891 * @param x first object to compare 892 * @param y second object to compare 893 * 894 * @return true if objects are not equal 895 */ 896 public static boolean NE_STRICT(final Object x, final Object y) { 897 return !EQ_STRICT(x, y); 898 } 899 900 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 901 private static boolean strictEquals(final Object x, final Object y) { 902 // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having 903 // NaN value is not equal to itself by value even though it is referentially. 904 905 final JSType xType = JSType.ofNoFunction(x); 906 final JSType yType = JSType.ofNoFunction(y); 907 908 if (xType != yType) { 909 return false; 910 } 911 912 return equalSameTypeValues(x, y, xType); 913 } 914 915 /** 916 * ECMA 11.8.6 - The in operator - generic implementation 917 * 918 * @param property property to check for 919 * @param obj object in which to check for property 920 * 921 * @return true if objects are equal 922 */ 923 public static boolean IN(final Object property, final Object obj) { 924 final JSType rvalType = JSType.ofNoFunction(obj); 925 926 if (rvalType == JSType.OBJECT) { 927 if (obj instanceof ScriptObject) { 928 return ((ScriptObject)obj).has(property); 929 } 930 931 if (obj instanceof JSObject) { 932 return ((JSObject)obj).hasMember(Objects.toString(property)); 933 } 934 935 return false; 936 } 937 938 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 939 } 940 941 /** 942 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 943 * 944 * @param obj first object to compare 945 * @param clazz type to check against 946 * 947 * @return true if {@code obj} is an instanceof {@code clazz} 948 */ 949 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 950 if (clazz instanceof ScriptFunction) { 951 if (obj instanceof ScriptObject) { 952 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 953 } 954 return false; 955 } 956 957 if (clazz instanceof StaticClass) { 958 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 959 } 960 961 if (clazz instanceof JSObject) { 962 return ((JSObject)clazz).isInstance(obj); 963 } 964 965 // provide for reverse hook 966 if (obj instanceof JSObject) { 967 return ((JSObject)obj).isInstanceOf(clazz); 968 } 969 970 throw typeError("instanceof.on.non.object"); 971 } 972 973 /** 974 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 975 * 976 * @param x first object to compare 977 * @param y second object to compare 978 * 979 * @return true if x is less than y 980 */ 981 public static boolean LT(final Object x, final Object y) { 982 final Object px = JSType.toPrimitive(x, Number.class); 983 final Object py = JSType.toPrimitive(y, Number.class); 984 985 return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : 986 JSType.toNumber(px) < JSType.toNumber(py); 987 } 988 989 private static boolean areBothString(final Object x, final Object y) { 990 return isString(x) && isString(y); 991 } 992 993 /** 994 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 995 * 996 * @param x first object to compare 997 * @param y second object to compare 998 * 999 * @return true if x is greater than y 1000 */ 1001 public static boolean GT(final Object x, final Object y) { 1002 final Object px = JSType.toPrimitive(x, Number.class); 1003 final Object py = JSType.toPrimitive(y, Number.class); 1004 1005 return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : 1006 JSType.toNumber(px) > JSType.toNumber(py); 1007 } 1008 1009 /** 1010 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation 1011 * 1012 * @param x first object to compare 1013 * @param y second object to compare 1014 * 1015 * @return true if x is less than or equal to y 1016 */ 1017 public static boolean LE(final Object x, final Object y) { 1018 final Object px = JSType.toPrimitive(x, Number.class); 1019 final Object py = JSType.toPrimitive(y, Number.class); 1020 1021 return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : 1022 JSType.toNumber(px) <= JSType.toNumber(py); 1023 } 1024 1025 /** 1026 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 1027 * 1028 * @param x first object to compare 1029 * @param y second object to compare 1030 * 1031 * @return true if x is greater than or equal to y 1032 */ 1033 public static boolean GE(final Object x, final Object y) { 1034 final Object px = JSType.toPrimitive(x, Number.class); 1035 final Object py = JSType.toPrimitive(y, Number.class); 1036 1037 return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : 1038 JSType.toNumber(px) >= JSType.toNumber(py); 1039 } 1040 1041 /** 1042 * Tag a reserved name as invalidated - used when someone writes 1043 * to a property with this name - overly conservative, but link time 1044 * is too late to apply e.g. apply->call specialization 1045 * @param name property name 1046 */ 1047 public static void invalidateReservedBuiltinName(final String name) { 1048 final Context context = Context.getContextTrusted(); 1049 final SwitchPoint sp = context.getBuiltinSwitchPoint(name); 1050 assert sp != null; 1051 context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); 1052 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 1053 } 1054 1055 /** 1056 * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral). 1057 * 1058 * @param rawStrings array of template raw values 1059 * @param cookedStrings array of template values 1060 * @return template object 1061 */ 1062 public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) { 1063 final ScriptObject template = (ScriptObject)cookedStrings; 1064 final ScriptObject rawObj = (ScriptObject)rawStrings; 1065 assert rawObj.getArray().length() == template.getArray().length(); 1066 template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze()); 1067 template.freeze(); 1068 return template; 1069 } 1070} 1071