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