src/jdk/nashorn/internal/runtime/ScriptRuntime.java

Fri, 20 Feb 2015 15:47:28 +0100

author
attila
date
Fri, 20 Feb 2015 15:47:28 +0100
changeset 1250
9ee1fc3f6136
parent 1243
2b51c0b3f463
child 1251
85a6a7545dbe
permissions
-rw-r--r--

8072426: Can't compare Java objects to strings or numbers
Reviewed-by: hannesw, lagergren, sundar

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

mercurial