1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
  29 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
  30 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
  31 import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError;
  32 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  33 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt;
  34 
  35 import java.lang.invoke.MethodHandle;
  36 import java.lang.reflect.Array;
  37 import java.util.Collections;
  38 import java.util.Iterator;
  39 import java.util.Locale;
  40 import java.util.NoSuchElementException;
  41 import java.util.Objects;
  42 import jdk.internal.dynalink.beans.StaticClass;
  43 import jdk.nashorn.api.scripting.ScriptObjectMirror;
  44 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
  45 import jdk.nashorn.internal.ir.debug.JSONWriter;
  46 import jdk.nashorn.internal.parser.Lexer;
  47 import jdk.nashorn.internal.runtime.linker.Bootstrap;
  48 
  49 
  50 /**
  51  * Utilities to be called by JavaScript runtime API and generated classes.
  52  */
  53 
  54 public final class ScriptRuntime {
  55     private ScriptRuntime() {
  56     }
  57 
  58     /** Singleton representing the empty array object '[]' */
  59     public static final Object[] EMPTY_ARRAY = new Object[0];
  60 
  61     /** Unique instance of undefined. */
  62     public static final Undefined UNDEFINED = Undefined.getUndefined();
  63 
  64     /**
  65      * Unique instance of undefined used to mark empty array slots.
  66      * Can't escape the array.
  67      */
  68     public static final Undefined EMPTY = Undefined.getEmpty();
  69 
  70     /** Method handle to generic + operator, operating on objects */
  71     public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class);
  72 
  73     /** Method handle to generic === operator, operating on objects */
  74     public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class);
  75 
  76     /** Method handle used to enter a {@code with} scope at runtime. */
  77     public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class);
  78 
  79     /** Method handle used to exit a {@code with} scope at runtime. */
  80     public static final Call CLOSE_WITH = staticCallNoLookup(ScriptRuntime.class, "closeWith", ScriptObject.class, ScriptObject.class);
  81 
  82     /**
  83      * Method used to place a scope's variable into the Global scope, which has to be done for the
  84      * properties declared at outermost script level.
  85      */
  86     public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class);
  87 
  88     /**
  89      * Return an appropriate iterator for the elements in a for-in construct
  90      */
  91     public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class);
  92 
  93     /**
  94      * Return an appropriate iterator for the elements in a for-each construct
  95      */
  96     public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class);
  97 
  98     /**
  99       * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to
 100       * call sites that are known to be megamorphic. Using an invoke dynamic here would
 101       * lead to the JVM deoptimizing itself to death
 102       */
 103     public static final Call APPLY = staticCall(ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class);
 104 
 105     /**
 106      * Converts a switch tag value to a simple integer. deflt value if it can't.
 107      *
 108      * @param tag   Switch statement tag value.
 109      * @param deflt default to use if not convertible.
 110      * @return int tag value (or deflt.)
 111      */
 112     public static int switchTagAsInt(final Object tag, final int deflt) {
 113         if (tag instanceof Number) {
 114             final double d = ((Number)tag).doubleValue();
 115             if (isRepresentableAsInt(d)) {
 116                 return (int)d;
 117             }
 118         }
 119 
 120         return deflt;
 121     }
 122 
 123     /**
 124      * Converts a switch tag value to a simple integer. deflt value if it can't.
 125      *
 126      * @param tag   Switch statement tag value.
 127      * @param deflt default to use if not convertible.
 128      * @return int tag value (or deflt.)
 129      */
 130     public static int switchTagAsInt(final long tag, final int deflt) {
 131         return isRepresentableAsInt(tag) ? (int)tag : deflt;
 132     }
 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 double tag, final int deflt) {
 142         return isRepresentableAsInt(tag) ? (int)tag : deflt;
 143     }
 144 
 145     /**
 146      * This is the builtin implementation of {@code Object.prototype.toString}
 147      * @param self reference
 148      * @return string representation as object
 149      */
 150     public static String builtinObjectToString(final Object self) {
 151         String className;
 152         // Spec tells us to convert primitives by ToObject..
 153         // But we don't need to -- all we need is the right class name
 154         // of the corresponding primitive wrapper type.
 155 
 156         final JSType type = JSType.of(self);
 157 
 158         switch (type) {
 159         case BOOLEAN:
 160             className = "Boolean";
 161             break;
 162         case NUMBER:
 163             className = "Number";
 164             break;
 165         case STRING:
 166             className = "String";
 167             break;
 168         // special case of null and undefined
 169         case NULL:
 170             className = "Null";
 171             break;
 172         case UNDEFINED:
 173             className = "Undefined";
 174             break;
 175         case OBJECT:
 176         case FUNCTION:
 177             if (self instanceof ScriptObject) {
 178                 className = ((ScriptObject)self).getClassName();
 179             } else {
 180                 className = self.getClass().getName();
 181             }
 182             break;
 183         default:
 184             // Nashorn extension: use Java class name
 185             className = self.getClass().getName();
 186             break;
 187         }
 188 
 189         final StringBuilder sb = new StringBuilder();
 190         sb.append("[object ");
 191         sb.append(className);
 192         sb.append(']');
 193 
 194         return sb.toString();
 195     }
 196 
 197     /**
 198      * This is called whenever runtime wants to throw an error and wants to provide
 199      * meaningful information about an object. We don't want to call toString which
 200      * ends up calling "toString" from script world which may itself throw error.
 201      * When we want to throw an error, we don't additional error from script land
 202      * -- which may sometimes lead to infinite recursion.
 203      *
 204      * @param obj Object to converted to String safely (without calling user script)
 205      * @return safe String representation of the given object
 206      */
 207     public static String safeToString(final Object obj) {
 208         return JSType.toStringImpl(obj, true);
 209     }
 210 
 211     /**
 212      * Used to determine property iterator used in for in.
 213      * @param obj Object to iterate on.
 214      * @return Iterator.
 215      */
 216     public static Iterator<String> toPropertyIterator(final Object obj) {
 217         if (obj instanceof ScriptObject) {
 218             return ((ScriptObject)obj).propertyIterator();
 219         }
 220 
 221         if (obj != null && obj.getClass().isArray()) {
 222             final int length = Array.getLength(obj);
 223 
 224             return new Iterator<String>() {
 225                 private int index = 0;
 226 
 227                 @Override
 228                 public boolean hasNext() {
 229                     return index < length;
 230                 }
 231 
 232                 @Override
 233                 public String next() {
 234                     return "" + index++; //TODO numeric property iterator?
 235                 }
 236 
 237                 @Override
 238                 public void remove() {
 239                     throw new UnsupportedOperationException();
 240                 }
 241             };
 242         }
 243 
 244         if (obj instanceof ScriptObjectMirror) {
 245             return ((ScriptObjectMirror)obj).keySet().iterator();
 246         }
 247 
 248         return Collections.emptyIterator();
 249     }
 250 
 251     /**
 252      * Used to determine property value iterator used in for each in.
 253      * @param obj Object to iterate on.
 254      * @return Iterator.
 255      */
 256     public static Iterator<?> toValueIterator(final Object obj) {
 257         if (obj instanceof ScriptObject) {
 258             return ((ScriptObject)obj).valueIterator();
 259         }
 260 
 261         if (obj != null && obj.getClass().isArray()) {
 262             final Object array  = obj;
 263             final int    length = Array.getLength(obj);
 264 
 265             return new Iterator<Object>() {
 266                 private int index = 0;
 267 
 268                 @Override
 269                 public boolean hasNext() {
 270                     return index < length;
 271                 }
 272 
 273                 @Override
 274                 public Object next() {
 275                     if (index >= length) {
 276                         throw new NoSuchElementException();
 277                     }
 278                     return Array.get(array, index++);
 279                 }
 280 
 281                 @Override
 282                 public void remove() {
 283                     throw new UnsupportedOperationException();
 284                 }
 285             };
 286         }
 287 
 288         if (obj instanceof ScriptObjectMirror) {
 289             return ((ScriptObjectMirror)obj).values().iterator();
 290         }
 291 
 292         if (obj instanceof Iterable) {
 293             return ((Iterable<?>)obj).iterator();
 294         }
 295 
 296         return Collections.emptyIterator();
 297     }
 298 
 299     /**
 300      * Merge a scope into its prototype's map.
 301      * Merge a scope into its prototype.
 302      *
 303      * @param scope Scope to merge.
 304      * @return prototype object after merge
 305      */
 306     public static ScriptObject mergeScope(final ScriptObject scope) {
 307         final ScriptObject global = scope.getProto();
 308         global.addBoundProperties(scope);
 309         return global;
 310     }
 311 
 312     /**
 313      * Check that the target function is associated with current Context. And also make sure that 'self', if
 314      * ScriptObject, is from current context.
 315      *
 316      * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve
 317      * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker}
 318      * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead.
 319      *
 320      * @param target ScriptFunction object.
 321      * @param self   Receiver in call.
 322      * @param args   Call arguments.
 323      * @return Call result.
 324      */
 325     public static Object checkAndApply(final ScriptFunction target, final Object self, final Object... args) {
 326         final ScriptObject global = Context.getGlobalTrusted();
 327         if (! (global instanceof GlobalObject)) {
 328             throw new IllegalStateException("No current global set");
 329         }
 330 
 331         if (target.getContext() != global.getContext()) {
 332             throw new IllegalArgumentException("'target' function is not from current Context");
 333         }
 334 
 335         if (self instanceof ScriptObject && ((ScriptObject)self).getContext() != global.getContext()) {
 336             throw new IllegalArgumentException("'self' object is not from current Context");
 337         }
 338 
 339         // all in order - call real 'apply'
 340         return apply(target, self, args);
 341     }
 342 
 343     /**
 344      * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve
 345      * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker}
 346      * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead.
 347      *
 348      * @param target ScriptFunction object.
 349      * @param self   Receiver in call.
 350      * @param args   Call arguments.
 351      * @return Call result.
 352      */
 353     public static Object apply(final ScriptFunction target, final Object self, final Object... args) {
 354         try {
 355             return target.invoke(self, args);
 356         } catch (final RuntimeException | Error e) {
 357             throw e;
 358         } catch (final Throwable t) {
 359             throw new RuntimeException(t);
 360         }
 361     }
 362 
 363     /**
 364      * Check that the target function is associated with current Context.
 365      * And also make sure that 'self', if ScriptObject, is from current context.
 366      *
 367      * Call a function as a constructor given args.
 368      *
 369      * @param target ScriptFunction object.
 370      * @param args   Call arguments.
 371      * @return Constructor call result.
 372      */
 373     public static Object checkAndConstruct(final ScriptFunction target, final Object... args) {
 374         final ScriptObject global = Context.getGlobalTrusted();
 375         if (! (global instanceof GlobalObject)) {
 376             throw new IllegalStateException("No current global set");
 377         }
 378 
 379         if (target.getContext() != global.getContext()) {
 380             throw new IllegalArgumentException("'target' function is not from current Context");
 381         }
 382 
 383         // all in order - call real 'construct'
 384         return construct(target, args);
 385     }
 386 
 387     /*
 388      * Call a script function as a constructor with given args.
 389      *
 390      * @param target ScriptFunction object.
 391      * @param args   Call arguments.
 392      * @return Constructor call result.
 393      */
 394     public static Object construct(final ScriptFunction target, final Object... args) {
 395         try {
 396             return target.construct(args);
 397         } catch (final RuntimeException | Error e) {
 398             throw e;
 399         } catch (final Throwable t) {
 400             throw new RuntimeException(t);
 401         }
 402     }
 403 
 404     /**
 405      * Generic implementation of ECMA 9.12 - SameValue algorithm
 406      *
 407      * @param x first value to compare
 408      * @param y second value to compare
 409      *
 410      * @return true if both objects have the same value
 411      */
 412     public static boolean sameValue(final Object x, final Object y) {
 413         final JSType xType = JSType.of(x);
 414         final JSType yType = JSType.of(y);
 415 
 416         if (xType != yType) {
 417             return false;
 418         }
 419 
 420         if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
 421             return true;
 422         }
 423 
 424         if (xType == JSType.NUMBER) {
 425             final double xVal = ((Number)x).doubleValue();
 426             final double yVal = ((Number)y).doubleValue();
 427 
 428             if (Double.isNaN(xVal) && Double.isNaN(yVal)) {
 429                 return true;
 430             }
 431 
 432             // checking for xVal == -0.0 and yVal == +0.0 or vice versa
 433             if (xVal == 0.0 && (Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal))) {
 434                 return false;
 435             }
 436 
 437             return xVal == yVal;
 438         }
 439 
 440         if (xType == JSType.STRING || yType == JSType.BOOLEAN) {
 441             return x.equals(y);
 442         }
 443 
 444         return (x == y);
 445     }
 446 
 447     /**
 448      * Returns AST as JSON compatible string. This is used to
 449      * implement "parse" function in resources/parse.js script.
 450      *
 451      * @param code code to be parsed
 452      * @param name name of the code source (used for location)
 453      * @param includeLoc tells whether to include location information for nodes or not
 454      * @return JSON string representation of AST of the supplied code
 455      */
 456     public static String parse(final String code, final String name, final boolean includeLoc) {
 457         return JSONWriter.parse(Context.getContextTrusted().getEnv(), code, name, includeLoc);
 458     }
 459 
 460     /**
 461      * Test whether a char is valid JavaScript whitespace
 462      * @param ch a char
 463      * @return true if valid JavaScript whitespace
 464      */
 465     public static boolean isJSWhitespace(final char ch) {
 466         return Lexer.isJSWhitespace(ch);
 467     }
 468 
 469     /**
 470      * Entering a {@code with} node requires new scope. This is the implementation
 471      *
 472      * @param scope      existing scope
 473      * @param expression expression in with
 474      *
 475      * @return {@link WithObject} that is the new scope
 476      */
 477     public static ScriptObject openWith(final ScriptObject scope, final Object expression) {
 478         final ScriptObject global = Context.getGlobalTrusted();
 479         if (expression == UNDEFINED) {
 480             throw typeError(global, "cant.apply.with.to.undefined");
 481         } else if (expression == null) {
 482             throw typeError(global, "cant.apply.with.to.null");
 483         }
 484 
 485         final ScriptObject withObject = new WithObject(scope, JSType.toScriptObject(global, expression));
 486 
 487         return withObject;
 488     }
 489 
 490     /**
 491      * Exiting a {@code with} node requires restoring scope. This is the implementation
 492      *
 493      * @param scope existing scope
 494      *
 495      * @return restored scope
 496      */
 497     public static ScriptObject closeWith(final ScriptObject scope) {
 498         if (scope instanceof WithObject) {
 499             return scope.getProto();
 500         }
 501         return scope;
 502     }
 503 
 504     /**
 505      * ECMA 11.6.1 - The addition operator (+) - generic implementation
 506      * Compiler specializes using {@link jdk.nashorn.internal.codegen.RuntimeCallSite}
 507      * if any type information is available for any of the operands
 508      *
 509      * @param x  first term
 510      * @param y  second term
 511      *
 512      * @return result of addition
 513      */
 514     public static Object ADD(final Object x, final Object y) {
 515         // This prefix code to handle Number special is for optimization.
 516         final boolean xIsNumber = x instanceof Number;
 517         final boolean yIsNumber = y instanceof Number;
 518 
 519         if (xIsNumber && yIsNumber) {
 520              return ((Number)x).doubleValue() + ((Number)y).doubleValue();
 521         }
 522 
 523         final boolean xIsUndefined = x == UNDEFINED;
 524         final boolean yIsUndefined = y == UNDEFINED;
 525 
 526         if ((xIsNumber && yIsUndefined) || (xIsUndefined && yIsNumber) || (xIsUndefined && yIsUndefined)) {
 527             return Double.NaN;
 528         }
 529 
 530         // code below is as per the spec.
 531         final Object xPrim = JSType.toPrimitive(x);
 532         final Object yPrim = JSType.toPrimitive(y);
 533 
 534         if (xPrim instanceof String || yPrim instanceof String
 535                 || xPrim instanceof ConsString || yPrim instanceof ConsString) {
 536             return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim));
 537         }
 538 
 539         return JSType.toNumber(xPrim) + JSType.toNumber(yPrim);
 540     }
 541 
 542     /**
 543      * Debugger hook.
 544      * TODO: currently unimplemented
 545      *
 546      * @return undefined
 547      */
 548     public static Object DEBUGGER() {
 549         return UNDEFINED;
 550     }
 551 
 552     /**
 553      * New hook
 554      *
 555      * @param clazz type for the clss
 556      * @param args  constructor arguments
 557      *
 558      * @return undefined
 559      */
 560     public static Object NEW(final Object clazz, final Object... args) {
 561         return UNDEFINED;
 562     }
 563 
 564     /**
 565      * ECMA 11.4.3 The typeof Operator - generic implementation
 566      *
 567      * @param object   the object from which to retrieve property to type check
 568      * @param property property in object to check
 569      *
 570      * @return type name
 571      */
 572     public static Object TYPEOF(final Object object, final Object property) {
 573         Object obj = object;
 574 
 575         if (property != null) {
 576             if (obj instanceof ScriptObject) {
 577                 obj = ((ScriptObject)obj).get(property);
 578             } else if (object instanceof Undefined) {
 579                 obj = ((Undefined)obj).get(property);
 580             } else if (object == null) {
 581                 throw typeError("cant.get.property", safeToString(property), "null");
 582             } else if (JSType.isPrimitive(obj)) {
 583                 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property);
 584             } else {
 585                 obj = UNDEFINED;
 586             }
 587         }
 588 
 589         return JSType.of(obj).typeName();
 590     }
 591 
 592     /**
 593      * ECMA 11.4.2 - void operator
 594      *
 595      * @param object object to evaluate
 596      *
 597      * @return Undefined as the object type
 598      */
 599     public static Object VOID(final Object object) {
 600         if (object instanceof Number) {
 601             if (Double.isNaN(((Number)object).doubleValue())) {
 602                 return Double.NaN;
 603             }
 604         }
 605 
 606         return UNDEFINED;
 607     }
 608 
 609     /**
 610      * Throw ReferenceError when LHS of assignment or increment/decrement
 611      * operator is not an assignable node (say a literal)
 612      *
 613      * @param lhs Evaluated LHS
 614      * @param rhs Evaluated RHS
 615      * @param msg Additional LHS info for error message
 616      * @return undefined
 617      */
 618     public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) {
 619         throw referenceError("cant.be.used.as.lhs", Objects.toString(msg));
 620     }
 621 
 622     /**
 623      * ECMA 11.4.1 - delete operation, generic implementation
 624      *
 625      * @param obj       object with property to delete
 626      * @param property  property to delete
 627      * @param strict    are we in strict mode
 628      *
 629      * @return true if property was successfully found and deleted
 630      */
 631     public static boolean DELETE(final Object obj, final Object property, final Object strict) {
 632         if (obj instanceof ScriptObject) {
 633             return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict));
 634         }
 635 
 636         if (obj instanceof Undefined) {
 637             return ((Undefined)obj).delete(property, false);
 638         }
 639 
 640         if (obj == null) {
 641             throw typeError("cant.delete.property", safeToString(property), "null");
 642         }
 643 
 644         if (obj instanceof ScriptObjectMirror) {
 645             return ((ScriptObjectMirror)obj).delete(property);
 646         }
 647 
 648         if (JSType.isPrimitive(obj)) {
 649             return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict));
 650         }
 651 
 652         // if object is not reference type, vacuously delete is successful.
 653         return true;
 654     }
 655 
 656     /**
 657      * ECMA 11.4.1 - delete operator, special case
 658      *
 659      * This is 'delete' that always fails. We have to check strict mode and throw error.
 660      * That is why this is a runtime function. Or else we could have inlined 'false'.
 661      *
 662      * @param property  property to delete
 663      * @param strict    are we in strict mode
 664      *
 665      * @return false always
 666      */
 667     public static boolean FAIL_DELETE(final Object property, final Object strict) {
 668         if (Boolean.TRUE.equals(strict)) {
 669             throw syntaxError("strict.cant.delete", safeToString(property));
 670         }
 671         return false;
 672     }
 673 
 674     /**
 675      * ECMA 11.9.1 - The equals operator (==) - generic implementation
 676      *
 677      * @param x first object to compare
 678      * @param y second object to compare
 679      *
 680      * @return true if type coerced versions of objects are equal
 681      */
 682     public static boolean EQ(final Object x, final Object y) {
 683         return equals(x, y);
 684     }
 685 
 686     /**
 687      * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation
 688      *
 689      * @param x first object to compare
 690      * @param y second object to compare
 691      *
 692      * @return true if type coerced versions of objects are not equal
 693      */
 694     public static boolean NE(final Object x, final Object y) {
 695         return !EQ(x, y);
 696     }
 697 
 698     /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */
 699     private static boolean equals(final Object x, final Object y) {
 700         final JSType xType = JSType.of(x);
 701         final JSType yType = JSType.of(y);
 702 
 703         if (xType == yType) {
 704 
 705             if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
 706                 return true;
 707             }
 708 
 709             if (xType == JSType.NUMBER) {
 710                 final double xVal = ((Number)x).doubleValue();
 711                 final double yVal = ((Number)y).doubleValue();
 712                 if (Double.isNaN(xVal) || Double.isNaN(yVal)) {
 713                     return false;
 714                 }
 715 
 716                 return xVal == yVal;
 717             }
 718 
 719             if (xType == JSType.STRING) {
 720                 // String may be represented by ConsString
 721                 return x.toString().equals(y.toString());
 722             }
 723 
 724             if (xType == JSType.BOOLEAN) {
 725                 // Boolean comparison
 726                 return x.equals(y);
 727             }
 728 
 729             return x == y;
 730         }
 731 
 732         if ((xType == JSType.UNDEFINED && yType == JSType.NULL) ||
 733             (xType == JSType.NULL && yType == JSType.UNDEFINED)) {
 734             return true;
 735         }
 736 
 737         if (xType == JSType.NUMBER && yType == JSType.STRING) {
 738             return EQ(x, JSType.toNumber(y));
 739         }
 740 
 741         if (xType == JSType.STRING && yType == JSType.NUMBER) {
 742             return EQ(JSType.toNumber(x), y);
 743         }
 744 
 745         if (xType == JSType.BOOLEAN) {
 746             return EQ(JSType.toNumber(x), y);
 747         }
 748 
 749         if (yType == JSType.BOOLEAN) {
 750             return EQ(x, JSType.toNumber(y));
 751         }
 752 
 753         if ((xType == JSType.STRING || xType == JSType.NUMBER) &&
 754              (y instanceof ScriptObject))  {
 755             return EQ(x, JSType.toPrimitive(y));
 756         }
 757 
 758         if ((x instanceof ScriptObject) &&
 759             (yType == JSType.STRING || yType == JSType.NUMBER)) {
 760             return EQ(JSType.toPrimitive(x), y);
 761         }
 762 
 763         return false;
 764     }
 765 
 766     /**
 767      * ECMA 11.9.4 - The strict equal operator (===) - generic implementation
 768      *
 769      * @param x first object to compare
 770      * @param y second object to compare
 771      *
 772      * @return true if objects are equal
 773      */
 774     public static boolean EQ_STRICT(final Object x, final Object y) {
 775         return strictEquals(x, y);
 776     }
 777 
 778     /**
 779      * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation
 780      *
 781      * @param x first object to compare
 782      * @param y second object to compare
 783      *
 784      * @return true if objects are not equal
 785      */
 786     public static boolean NE_STRICT(final Object x, final Object y) {
 787         return !EQ_STRICT(x, y);
 788     }
 789 
 790     /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */
 791     private static boolean strictEquals(final Object x, final Object y) {
 792         final JSType xType = JSType.of(x);
 793         final JSType yType = JSType.of(y);
 794 
 795         if (xType != yType) {
 796             return false;
 797         }
 798 
 799         if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
 800             return true;
 801         }
 802 
 803         if (xType == JSType.NUMBER) {
 804             final double xVal = ((Number)x).doubleValue();
 805             final double yVal = ((Number)y).doubleValue();
 806 
 807             if (Double.isNaN(xVal) || Double.isNaN(yVal)) {
 808                 return false;
 809             }
 810 
 811             return xVal == yVal;
 812         }
 813 
 814         if (xType == JSType.STRING) {
 815             // String may be represented by ConsString
 816             return x.toString().equals(y.toString());
 817         }
 818 
 819         if (xType == JSType.BOOLEAN) {
 820             return x.equals(y);
 821         }
 822 
 823         // finally, the object identity comparison
 824         return x == y;
 825     }
 826 
 827     /**
 828      * ECMA 11.8.6 - The in operator - generic implementation
 829      *
 830      * @param property property to check for
 831      * @param obj object in which to check for property
 832      *
 833      * @return true if objects are equal
 834      */
 835     public static boolean IN(final Object property, final Object obj) {
 836         final JSType rvalType = JSType.of(obj);
 837 
 838         if (rvalType == JSType.OBJECT || rvalType == JSType.FUNCTION) {
 839             if (obj instanceof ScriptObject) {
 840                 return ((ScriptObject)obj).has(property);
 841             }
 842 
 843             return false;
 844         }
 845 
 846         throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH));
 847     }
 848 
 849     /**
 850      * ECMA 11.8.6 - The strict instanceof operator - generic implementation
 851      *
 852      * @param obj first object to compare
 853      * @param clazz type to check against
 854      *
 855      * @return true if {@code obj} is an instanceof {@code clazz}
 856      */
 857     public static boolean INSTANCEOF(final Object obj, final Object clazz) {
 858         if (clazz instanceof ScriptFunction) {
 859             if (obj instanceof ScriptObject) {
 860                 return ((ScriptObject)clazz).isInstance((ScriptObject)obj);
 861             }
 862             return false;
 863         }
 864 
 865         if (clazz instanceof StaticClass) {
 866             return ((StaticClass)clazz).getRepresentedClass().isInstance(obj);
 867         }
 868 
 869         if (clazz instanceof ScriptObjectMirror) {
 870             if (obj instanceof ScriptObjectMirror) {
 871                 return ((ScriptObjectMirror)clazz).isInstance((ScriptObjectMirror)obj);
 872             }
 873             return false;
 874         }
 875 
 876         throw typeError("instanceof.on.non.object");
 877     }
 878 
 879     /**
 880      * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation
 881      *
 882      * @param x first object to compare
 883      * @param y second object to compare
 884      *
 885      * @return true if x is less than y
 886      */
 887     public static boolean LT(final Object x, final Object y) {
 888         final Object value = lessThan(x, y, true);
 889         return (value == UNDEFINED) ? false : (Boolean)value;
 890     }
 891 
 892     /**
 893      * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation
 894      *
 895      * @param x first object to compare
 896      * @param y second object to compare
 897      *
 898      * @return true if x is greater than y
 899      */
 900     public static boolean GT(final Object x, final Object y) {
 901         final Object value = lessThan(y, x, false);
 902         return (value == UNDEFINED) ? false : (Boolean)value;
 903     }
 904 
 905     /**
 906      * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation
 907      *
 908      * @param x first object to compare
 909      * @param y second object to compare
 910      *
 911      * @return true if x is less than or equal to y
 912      */
 913     public static boolean LE(final Object x, final Object y) {
 914         final Object value = lessThan(y, x, false);
 915         return (!(Boolean.TRUE.equals(value) || value == UNDEFINED));
 916     }
 917 
 918     /**
 919      * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation
 920      *
 921      * @param x first object to compare
 922      * @param y second object to compare
 923      *
 924      * @return true if x is greater than or equal to y
 925      */
 926     public static boolean GE(final Object x, final Object y) {
 927         final Object value = lessThan(x, y, true);
 928         return (!(Boolean.TRUE.equals(value) || value == UNDEFINED));
 929     }
 930 
 931     /** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */
 932     private static Object lessThan(final Object x, final Object y, final boolean leftFirst) {
 933         Object px, py;
 934 
 935         //support e.g. x < y should throw exception correctly if x or y are not numeric
 936         if (leftFirst) {
 937             px = JSType.toPrimitive(x, Number.class);
 938             py = JSType.toPrimitive(y, Number.class);
 939         } else {
 940             py = JSType.toPrimitive(y, Number.class);
 941             px = JSType.toPrimitive(x, Number.class);
 942         }
 943 
 944         if (JSType.of(px) == JSType.STRING && JSType.of(py) == JSType.STRING) {
 945             // May be String or ConsString
 946             return (px.toString()).compareTo(py.toString()) < 0;
 947         }
 948 
 949         final double nx = JSType.toNumber(px);
 950         final double ny = JSType.toNumber(py);
 951 
 952         if (Double.isNaN(nx) || Double.isNaN(ny)) {
 953             return UNDEFINED;
 954         }
 955 
 956         if (nx == ny) {
 957             return false;
 958         }
 959 
 960         if (nx > 0 && ny > 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) {
 961             return false;
 962         }
 963 
 964         if (nx < 0 && ny < 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) {
 965             return false;
 966         }
 967 
 968         return nx < ny;
 969     }
 970 
 971 }