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