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