/* * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; import static jdk.nashorn.internal.runtime.JSType.isString; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.SwitchPoint; import java.lang.reflect.Array; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import jdk.dynalink.beans.StaticClass; import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.api.scripting.ScriptObjectMirror; import jdk.nashorn.internal.codegen.ApplySpecialization; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.ir.debug.JSONWriter; import jdk.nashorn.internal.objects.AbstractIterator; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.objects.NativeObject; import jdk.nashorn.internal.parser.Lexer; import jdk.nashorn.internal.runtime.linker.Bootstrap; import jdk.nashorn.internal.runtime.linker.InvokeByName; /** * Utilities to be called by JavaScript runtime API and generated classes. */ public final class ScriptRuntime { private ScriptRuntime() { } /** Singleton representing the empty array object '[]' */ public static final Object[] EMPTY_ARRAY = new Object[0]; /** Unique instance of undefined. */ public static final Undefined UNDEFINED = Undefined.getUndefined(); /** * Unique instance of undefined used to mark empty array slots. * Can't escape the array. */ public static final Undefined EMPTY = Undefined.getEmpty(); /** Method handle to generic + operator, operating on objects */ public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class); /** Method handle to generic === operator, operating on objects */ public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class); /** Method handle used to enter a {@code with} scope at runtime. */ public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class); /** * Method used to place a scope's variable into the Global scope, which has to be done for the * properties declared at outermost script level. */ public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class); /** * Return an appropriate iterator for the elements in a for-in construct */ public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class); /** * Return an appropriate iterator for the elements in a for-each construct */ public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class); /** * Return an appropriate iterator for the elements in a ES6 for-of loop */ public static final Call TO_ES6_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toES6Iterator", Iterator.class, Object.class); /** * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to * call sites that are known to be megamorphic. Using an invoke dynamic here would * lead to the JVM deoptimizing itself to death */ public static final Call APPLY = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class); /** * Throws a reference error for an undefined variable. */ public static final Call THROW_REFERENCE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwReferenceError", void.class, String.class); /** * Throws a reference error for an undefined variable. */ public static final Call THROW_CONST_TYPE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwConstTypeError", void.class, String.class); /** * Used to invalidate builtin names, e.g "Function" mapping to all properties in Function.prototype and Function.prototype itself. */ public static final Call INVALIDATE_RESERVED_BUILTIN_NAME = staticCallNoLookup(ScriptRuntime.class, "invalidateReservedBuiltinName", void.class, String.class); /** * Converts a switch tag value to a simple integer. deflt value if it can't. * * @param tag Switch statement tag value. * @param deflt default to use if not convertible. * @return int tag value (or deflt.) */ public static int switchTagAsInt(final Object tag, final int deflt) { if (tag instanceof Number) { final double d = ((Number)tag).doubleValue(); if (isRepresentableAsInt(d)) { return (int)d; } } return deflt; } /** * Converts a switch tag value to a simple integer. deflt value if it can't. * * @param tag Switch statement tag value. * @param deflt default to use if not convertible. * @return int tag value (or deflt.) */ public static int switchTagAsInt(final boolean tag, final int deflt) { return deflt; } /** * Converts a switch tag value to a simple integer. deflt value if it can't. * * @param tag Switch statement tag value. * @param deflt default to use if not convertible. * @return int tag value (or deflt.) */ public static int switchTagAsInt(final long tag, final int deflt) { return isRepresentableAsInt(tag) ? (int)tag : deflt; } /** * Converts a switch tag value to a simple integer. deflt value if it can't. * * @param tag Switch statement tag value. * @param deflt default to use if not convertible. * @return int tag value (or deflt.) */ public static int switchTagAsInt(final double tag, final int deflt) { return isRepresentableAsInt(tag) ? (int)tag : deflt; } /** * This is the builtin implementation of {@code Object.prototype.toString} * @param self reference * @return string representation as object */ public static String builtinObjectToString(final Object self) { String className; // Spec tells us to convert primitives by ToObject.. // But we don't need to -- all we need is the right class name // of the corresponding primitive wrapper type. final JSType type = JSType.ofNoFunction(self); switch (type) { case BOOLEAN: className = "Boolean"; break; case NUMBER: className = "Number"; break; case STRING: className = "String"; break; // special case of null and undefined case NULL: className = "Null"; break; case UNDEFINED: className = "Undefined"; break; case OBJECT: if (self instanceof ScriptObject) { className = ((ScriptObject)self).getClassName(); } else if (self instanceof JSObject) { className = ((JSObject)self).getClassName(); } else { className = self.getClass().getName(); } break; default: // Nashorn extension: use Java class name className = self.getClass().getName(); break; } final StringBuilder sb = new StringBuilder(); sb.append("[object "); sb.append(className); sb.append(']'); return sb.toString(); } /** * This is called whenever runtime wants to throw an error and wants to provide * meaningful information about an object. We don't want to call toString which * ends up calling "toString" from script world which may itself throw error. * When we want to throw an error, we don't additional error from script land * -- which may sometimes lead to infinite recursion. * * @param obj Object to converted to String safely (without calling user script) * @return safe String representation of the given object */ public static String safeToString(final Object obj) { return JSType.toStringImpl(obj, true); } /** * Returns an iterator over property identifiers used in the {@code for...in} statement. Note that the ECMAScript * 5.1 specification, chapter 12.6.4. uses the terminology "property names", which seems to imply that the property * identifiers are expected to be strings, but this is not actually spelled out anywhere, and Nashorn will in some * cases deviate from this. Namely, we guarantee to always return an iterator over {@link String} values for any * built-in JavaScript object. We will however return an iterator over {@link Integer} objects for native Java * arrays and {@link List} objects, as well as arbitrary objects representing keys of a {@link Map}. Therefore, the * expression {@code typeof i} within a {@code for(i in obj)} statement can return something other than * {@code string} when iterating over native Java arrays, {@code List}, and {@code Map} objects. * @param obj object to iterate on. * @return iterator over the object's property names. */ public static Iterator<?> toPropertyIterator(final Object obj) { if (obj instanceof ScriptObject) { return ((ScriptObject)obj).propertyIterator(); } if (obj != null && obj.getClass().isArray()) { return new RangeIterator(Array.getLength(obj)); } if (obj instanceof JSObject) { return ((JSObject)obj).keySet().iterator(); } if (obj instanceof List) { return new RangeIterator(((List<?>)obj).size()); } if (obj instanceof Map) { return ((Map<?,?>)obj).keySet().iterator(); } final Object wrapped = Global.instance().wrapAsObject(obj); if (wrapped instanceof ScriptObject) { return ((ScriptObject)wrapped).propertyIterator(); } return Collections.emptyIterator(); } private static final class RangeIterator implements Iterator<Integer> { private final int length; private int index; RangeIterator(final int length) { this.length = length; } @Override public boolean hasNext() { return index < length; } @Override public Integer next() { return index++; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } // value Iterator for important Java objects - arrays, maps, iterables. private static Iterator<?> iteratorForJavaArrayOrList(final Object obj) { if (obj != null && obj.getClass().isArray()) { final Object array = obj; final int length = Array.getLength(obj); return new Iterator<Object>() { private int index = 0; @Override public boolean hasNext() { return index < length; } @Override public Object next() { if (index >= length) { throw new NoSuchElementException(); } return Array.get(array, index++); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } }; } if (obj instanceof Iterable) { return ((Iterable<?>)obj).iterator(); } return null; } /** * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over * map values. * @param obj object to iterate on. * @return iterator over the object's property values. */ public static Iterator<?> toValueIterator(final Object obj) { if (obj instanceof ScriptObject) { return ((ScriptObject)obj).valueIterator(); } if (obj instanceof JSObject) { return ((JSObject)obj).values().iterator(); } final Iterator<?> itr = iteratorForJavaArrayOrList(obj); if (itr != null) { return itr; } if (obj instanceof Map) { return ((Map<?,?>)obj).values().iterator(); } final Object wrapped = Global.instance().wrapAsObject(obj); if (wrapped instanceof ScriptObject) { return ((ScriptObject)wrapped).valueIterator(); } return Collections.emptyIterator(); } /** * Returns an iterator over property values used in the {@code for ... of} statement. The iterator uses the * Iterator interface defined in version 6 of the ECMAScript specification. * * @param obj object to iterate on. * @return iterator based on the ECMA 6 Iterator interface. */ public static Iterator<?> toES6Iterator(final Object obj) { // if not a ScriptObject, try convenience iterator for Java objects! if (!(obj instanceof ScriptObject)) { final Iterator<?> itr = iteratorForJavaArrayOrList(obj); if (itr != null) { return itr; } } final Global global = Global.instance(); final Object iterator = AbstractIterator.getIterator(Global.toObject(obj), global); final InvokeByName nextInvoker = AbstractIterator.getNextInvoker(global); final MethodHandle doneInvoker = AbstractIterator.getDoneInvoker(global); final MethodHandle valueInvoker = AbstractIterator.getValueInvoker(global); return new Iterator<Object>() { private Object nextResult = nextResult(); private Object nextResult() { try { final Object next = nextInvoker.getGetter().invokeExact(iterator); if (Bootstrap.isCallable(next)) { return nextInvoker.getInvoker().invokeExact(next, iterator, (Object) null); } } catch (final RuntimeException|Error r) { throw r; } catch (final Throwable t) { throw new RuntimeException(t); } return null; } @Override public boolean hasNext() { if (nextResult == null) { return false; } try { final Object done = doneInvoker.invokeExact(nextResult); return !JSType.toBoolean(done); } catch (final RuntimeException|Error r) { throw r; } catch (final Throwable t) { throw new RuntimeException(t); } } @Override public Object next() { if (nextResult == null) { return Undefined.getUndefined(); } try { final Object result = nextResult; nextResult = nextResult(); return valueInvoker.invokeExact(result); } catch (final RuntimeException|Error r) { throw r; } catch (final Throwable t) { throw new RuntimeException(t); } } @Override public void remove() { throw new UnsupportedOperationException("remove"); } }; } /** * Merge a scope into its prototype's map. * Merge a scope into its prototype. * * @param scope Scope to merge. * @return prototype object after merge */ public static ScriptObject mergeScope(final ScriptObject scope) { final ScriptObject parentScope = scope.getProto(); parentScope.addBoundProperties(scope); return parentScope; } /** * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve * better performance by creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)} * then using its {@link MethodHandle#invokeExact(Object...)} method instead. * * @param target ScriptFunction object. * @param self Receiver in call. * @param args Call arguments. * @return Call result. */ public static Object apply(final ScriptFunction target, final Object self, final Object... args) { try { return target.invoke(self, args); } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { throw new RuntimeException(t); } } /** * Throws a reference error for an undefined variable. * * @param name the variable name */ public static void throwReferenceError(final String name) { throw referenceError("not.defined", name); } /** * Throws a type error for an assignment to a const. * * @param name the const name */ public static void throwConstTypeError(final String name) { throw typeError("assign.constant", name); } /** * Call a script function as a constructor with given args. * * @param target ScriptFunction object. * @param args Call arguments. * @return Constructor call result. */ public static Object construct(final ScriptFunction target, final Object... args) { try { return target.construct(args); } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { throw new RuntimeException(t); } } /** * Generic implementation of ECMA 9.12 - SameValue algorithm * * @param x first value to compare * @param y second value to compare * * @return true if both objects have the same value */ public static boolean sameValue(final Object x, final Object y) { final JSType xType = JSType.ofNoFunction(x); final JSType yType = JSType.ofNoFunction(y); if (xType != yType) { return false; } if (xType == JSType.UNDEFINED || xType == JSType.NULL) { return true; } if (xType == JSType.NUMBER) { final double xVal = ((Number)x).doubleValue(); final double yVal = ((Number)y).doubleValue(); if (Double.isNaN(xVal) && Double.isNaN(yVal)) { return true; } // checking for xVal == -0.0 and yVal == +0.0 or vice versa if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) { return false; } return xVal == yVal; } if (xType == JSType.STRING || yType == JSType.BOOLEAN) { return x.equals(y); } return x == y; } /** * Returns AST as JSON compatible string. This is used to * implement "parse" function in resources/parse.js script. * * @param code code to be parsed * @param name name of the code source (used for location) * @param includeLoc tells whether to include location information for nodes or not * @return JSON string representation of AST of the supplied code */ public static String parse(final String code, final String name, final boolean includeLoc) { return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc); } /** * Test whether a char is valid JavaScript whitespace * @param ch a char * @return true if valid JavaScript whitespace */ public static boolean isJSWhitespace(final char ch) { return Lexer.isJSWhitespace(ch); } /** * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement, * use {@link ScriptObject#getProto()} on the scope. * * @param scope existing scope * @param expression expression in with * * @return {@link WithObject} that is the new scope */ public static ScriptObject openWith(final ScriptObject scope, final Object expression) { final Global global = Context.getGlobal(); if (expression == UNDEFINED) { throw typeError(global, "cant.apply.with.to.undefined"); } else if (expression == null) { throw typeError(global, "cant.apply.with.to.null"); } if (expression instanceof ScriptObjectMirror) { final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); if (unwrapped instanceof ScriptObject) { return new WithObject(scope, (ScriptObject)unwrapped); } // foreign ScriptObjectMirror final ScriptObject exprObj = global.newObject(); NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); return new WithObject(scope, exprObj); } final Object wrappedExpr = JSType.toScriptObject(global, expression); if (wrappedExpr instanceof ScriptObject) { return new WithObject(scope, (ScriptObject)wrappedExpr); } throw typeError(global, "cant.apply.with.to.non.scriptobject"); } /** * ECMA 11.6.1 - The addition operator (+) - generic implementation * * @param x first term * @param y second term * * @return result of addition */ public static Object ADD(final Object x, final Object y) { // This prefix code to handle Number special is for optimization. final boolean xIsNumber = x instanceof Number; final boolean yIsNumber = y instanceof Number; if (xIsNumber && yIsNumber) { return ((Number)x).doubleValue() + ((Number)y).doubleValue(); } final boolean xIsUndefined = x == UNDEFINED; final boolean yIsUndefined = y == UNDEFINED; if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) { return Double.NaN; } // code below is as per the spec. final Object xPrim = JSType.toPrimitive(x); final Object yPrim = JSType.toPrimitive(y); if (isString(xPrim) || isString(yPrim)) { try { return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); } catch (final IllegalArgumentException iae) { throw rangeError(iae, "concat.string.too.big"); } } return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); } /** * Debugger hook. * TODO: currently unimplemented * * @return undefined */ public static Object DEBUGGER() { return UNDEFINED; } /** * New hook * * @param clazz type for the clss * @param args constructor arguments * * @return undefined */ public static Object NEW(final Object clazz, final Object... args) { return UNDEFINED; } /** * ECMA 11.4.3 The typeof Operator - generic implementation * * @param object the object from which to retrieve property to type check * @param property property in object to check * * @return type name */ public static Object TYPEOF(final Object object, final Object property) { Object obj = object; if (property != null) { if (obj instanceof ScriptObject) { // this is a scope identifier assert property instanceof String; final ScriptObject sobj = (ScriptObject) obj; final FindProperty find = sobj.findProperty(property, true, true, sobj); if (find != null) { obj = find.getObjectValue(); } else { obj = sobj.invokeNoSuchProperty(property, false, UnwarrantedOptimismException.INVALID_PROGRAM_POINT); } if(Global.isLocationPropertyPlaceholder(obj)) { if(CompilerConstants.__LINE__.name().equals(property)) { obj = 0; } else { obj = ""; } } } else if (object instanceof Undefined) { obj = ((Undefined)obj).get(property); } else if (object == null) { throw typeError("cant.get.property", safeToString(property), "null"); } else if (JSType.isPrimitive(obj)) { obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); } else if (obj instanceof JSObject) { obj = ((JSObject)obj).getMember(property.toString()); } else { obj = UNDEFINED; } } return JSType.of(obj).typeName(); } /** * Throw ReferenceError when LHS of assignment or increment/decrement * operator is not an assignable node (say a literal) * * @param lhs Evaluated LHS * @param rhs Evaluated RHS * @param msg Additional LHS info for error message * @return undefined */ public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); } /** * ECMA 11.4.1 - delete operation, generic implementation * * @param obj object with property to delete * @param property property to delete * @param strict are we in strict mode * * @return true if property was successfully found and deleted */ public static boolean DELETE(final Object obj, final Object property, final Object strict) { if (obj instanceof ScriptObject) { return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); } if (obj instanceof Undefined) { return ((Undefined)obj).delete(property, false); } if (obj == null) { throw typeError("cant.delete.property", safeToString(property), "null"); } if (obj instanceof ScriptObjectMirror) { return ((ScriptObjectMirror)obj).delete(property); } if (JSType.isPrimitive(obj)) { return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); } if (obj instanceof JSObject) { ((JSObject)obj).removeMember(Objects.toString(property)); return true; } // if object is not reference type, vacuously delete is successful. return true; } /** * ECMA 11.4.1 - delete operator, implementation for slow scopes * * This implementation of 'delete' walks the scope chain to find the scope that contains the * property to be deleted, then invokes delete on it. * * @param obj top scope object * @param property property to delete * @param strict are we in strict mode * * @return true if property was successfully found and deleted */ public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) { if (obj instanceof ScriptObject) { ScriptObject sobj = (ScriptObject) obj; final String key = property.toString(); while (sobj != null && sobj.isScope()) { final FindProperty find = sobj.findProperty(key, false); if (find != null) { return sobj.delete(key, Boolean.TRUE.equals(strict)); } sobj = sobj.getProto(); } } return DELETE(obj, property, strict); } /** * ECMA 11.4.1 - delete operator, special case * * This is 'delete' that always fails. We have to check strict mode and throw error. * That is why this is a runtime function. Or else we could have inlined 'false'. * * @param property property to delete * @param strict are we in strict mode * * @return false always */ public static boolean FAIL_DELETE(final Object property, final Object strict) { if (Boolean.TRUE.equals(strict)) { throw syntaxError("strict.cant.delete", safeToString(property)); } return false; } /** * ECMA 11.9.1 - The equals operator (==) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if type coerced versions of objects are equal */ public static boolean EQ(final Object x, final Object y) { return equals(x, y); } /** * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if type coerced versions of objects are not equal */ public static boolean NE(final Object x, final Object y) { return !EQ(x, y); } /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ private static boolean equals(final Object x, final Object y) { // We want to keep this method small so we skip reference equality check for numbers // as NaN should return false when compared to itself (JDK-8043608). if (x == y && !(x instanceof Number)) { return true; } if (x instanceof ScriptObject && y instanceof ScriptObject) { return false; // x != y } if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) { return ScriptObjectMirror.identical(x, y); } return equalValues(x, y); } /** * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value * comparison applies). * @param x one value * @param y another value * @return true if they're equal according to 11.9.3 */ private static boolean equalValues(final Object x, final Object y) { final JSType xType = JSType.ofNoFunction(x); final JSType yType = JSType.ofNoFunction(y); if (xType == yType) { return equalSameTypeValues(x, y, xType); } return equalDifferentTypeValues(x, y, xType, yType); } /** * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares * values belonging to the same JSType. * @param x one value * @param y another value * @param type the common type for the values * @return true if they're equal */ private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) { if (type == JSType.UNDEFINED || type == JSType.NULL) { return true; } if (type == JSType.NUMBER) { return ((Number)x).doubleValue() == ((Number)y).doubleValue(); } if (type == JSType.STRING) { // String may be represented by ConsString return x.toString().equals(y.toString()); } if (type == JSType.BOOLEAN) { return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue(); } return x == y; } /** * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes. * @param x one value * @param y another value * @param xType the type for the value x * @param yType the type for the value y * @return true if they're equal */ private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) { if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) { return true; } else if (isNumberAndString(xType, yType)) { return equalNumberToString(x, y); } else if (isNumberAndString(yType, xType)) { // Can reverse order as both are primitives return equalNumberToString(y, x); } else if (xType == JSType.BOOLEAN) { return equalBooleanToAny(x, y); } else if (yType == JSType.BOOLEAN) { // Can reverse order as y is primitive return equalBooleanToAny(y, x); } else if (isPrimitiveAndObject(xType, yType)) { return equalWrappedPrimitiveToObject(x, y); } else if (isPrimitiveAndObject(yType, xType)) { // Can reverse order as y is primitive return equalWrappedPrimitiveToObject(y, x); } return false; } private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) { return xType == JSType.UNDEFINED && yType == JSType.NULL; } private static boolean isNumberAndString(final JSType xType, final JSType yType) { return xType == JSType.NUMBER && yType == JSType.STRING; } private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) { return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT; } private static boolean equalNumberToString(final Object num, final Object str) { // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number // comparison. return ((Number)num).doubleValue() == JSType.toNumber(str.toString()); } private static boolean equalBooleanToAny(final Object bool, final Object any) { return equals(JSType.toNumber((Boolean)bool), any); } private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) { return equals(numOrStr, JSType.toPrimitive(any)); } /** * ECMA 11.9.4 - The strict equal operator (===) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if objects are equal */ public static boolean EQ_STRICT(final Object x, final Object y) { return strictEquals(x, y); } /** * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if objects are not equal */ public static boolean NE_STRICT(final Object x, final Object y) { return !EQ_STRICT(x, y); } /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ private static boolean strictEquals(final Object x, final Object y) { // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having // NaN value is not equal to itself by value even though it is referentially. final JSType xType = JSType.ofNoFunction(x); final JSType yType = JSType.ofNoFunction(y); if (xType != yType) { return false; } return equalSameTypeValues(x, y, xType); } /** * ECMA 11.8.6 - The in operator - generic implementation * * @param property property to check for * @param obj object in which to check for property * * @return true if objects are equal */ public static boolean IN(final Object property, final Object obj) { final JSType rvalType = JSType.ofNoFunction(obj); if (rvalType == JSType.OBJECT) { if (obj instanceof ScriptObject) { return ((ScriptObject)obj).has(property); } if (obj instanceof JSObject) { return ((JSObject)obj).hasMember(Objects.toString(property)); } return false; } throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); } /** * ECMA 11.8.6 - The strict instanceof operator - generic implementation * * @param obj first object to compare * @param clazz type to check against * * @return true if {@code obj} is an instanceof {@code clazz} */ public static boolean INSTANCEOF(final Object obj, final Object clazz) { if (clazz instanceof ScriptFunction) { if (obj instanceof ScriptObject) { return ((ScriptObject)clazz).isInstance((ScriptObject)obj); } return false; } if (clazz instanceof StaticClass) { return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); } if (clazz instanceof JSObject) { return ((JSObject)clazz).isInstance(obj); } // provide for reverse hook if (obj instanceof JSObject) { return ((JSObject)obj).isInstanceOf(clazz); } throw typeError("instanceof.on.non.object"); } /** * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if x is less than y */ public static boolean LT(final Object x, final Object y) { final Object px = JSType.toPrimitive(x, Number.class); final Object py = JSType.toPrimitive(y, Number.class); return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : JSType.toNumber(px) < JSType.toNumber(py); } private static boolean areBothString(final Object x, final Object y) { return isString(x) && isString(y); } /** * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if x is greater than y */ public static boolean GT(final Object x, final Object y) { final Object px = JSType.toPrimitive(x, Number.class); final Object py = JSType.toPrimitive(y, Number.class); return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : JSType.toNumber(px) > JSType.toNumber(py); } /** * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if x is less than or equal to y */ public static boolean LE(final Object x, final Object y) { final Object px = JSType.toPrimitive(x, Number.class); final Object py = JSType.toPrimitive(y, Number.class); return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : JSType.toNumber(px) <= JSType.toNumber(py); } /** * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation * * @param x first object to compare * @param y second object to compare * * @return true if x is greater than or equal to y */ public static boolean GE(final Object x, final Object y) { final Object px = JSType.toPrimitive(x, Number.class); final Object py = JSType.toPrimitive(y, Number.class); return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : JSType.toNumber(px) >= JSType.toNumber(py); } /** * Tag a reserved name as invalidated - used when someone writes * to a property with this name - overly conservative, but link time * is too late to apply e.g. apply->call specialization * @param name property name */ public static void invalidateReservedBuiltinName(final String name) { final Context context = Context.getContextTrusted(); final SwitchPoint sp = context.getBuiltinSwitchPoint(name); assert sp != null; context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); } /** * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral). * * @param rawStrings array of template raw values * @param cookedStrings array of template values * @return template object */ public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) { final ScriptObject template = (ScriptObject)cookedStrings; final ScriptObject rawObj = (ScriptObject)rawStrings; assert rawObj.getArray().length() == template.getArray().length(); template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze()); template.freeze(); return template; } }