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