--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java 2020-04-15 18:50:08.000000000 +0530 +++ /dev/null 2020-04-15 18:50:08.000000000 +0530 @@ -1,1191 +0,0 @@ -/* - * Copyright (c) 2010, 2017, 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.BeansLinker; -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.objects.NativeJava; -import jdk.nashorn.internal.objects.NativeArray; -import jdk.nashorn.internal.parser.Lexer; -import jdk.nashorn.internal.runtime.arrays.ArrayIndex; -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); - - /** - * Used to perform failed delete under strict mode - */ - public static final Call STRICT_FAIL_DELETE = staticCallNoLookup(ScriptRuntime.class, "strictFailDelete", boolean.class, String.class); - - /** - * Used to find the scope for slow delete - */ - public static final Call SLOW_DELETE = staticCallNoLookup(ScriptRuntime.class, "slowDelete", boolean.class, ScriptObject.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 { - 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() { - 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; - } - - if (obj instanceof Map) { - return new Iterator() { - private Iterator iter = ((Map)obj).entrySet().iterator(); - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public Object next() { - Map.Entry next = (Map.Entry)iter.next(); - Object[] keyvalue = new Object[]{next.getKey(), next.getValue()}; - NativeArray array = NativeJava.from(null, keyvalue); - return array; - } - - @Override - public void remove() { - iter.remove(); - } - }; - } - } - - 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() { - - 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 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. Always used on scopes, never strict. - * - * @param obj top scope object - * @param property property to delete - * - * @return true if property was successfully found and deleted - */ - public static boolean slowDelete(final ScriptObject obj, final String property) { - ScriptObject sobj = obj; - while (sobj != null && sobj.isScope()) { - final FindProperty find = sobj.findProperty(property, false); - if (find != null) { - return sobj.delete(property, false); - } - sobj = sobj.getProto(); - } - return obj.delete(property, false); - } - - /** - * ECMA 11.4.1 - delete operator, special case - * - * This is 'delete' on a scope; it always fails under strict mode. - * It always throws an exception, but is declared to return a boolean - * to be compatible with the delete operator type. - * - * @param property property to delete - * @return nothing, always throws an exception. - */ - public static boolean strictFailDelete(final String property) { - throw syntaxError("strict.cant.delete", property); - } - - /** - * 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)); - } - - final Object key = JSType.toPropertyKey(property); - - if (obj instanceof StaticClass) { - final Class clazz = ((StaticClass) obj).getRepresentedClass(); - return BeansLinker.getReadableStaticPropertyNames(clazz).contains(Objects.toString(key)) - || BeansLinker.getStaticMethodNames(clazz).contains(Objects.toString(key)); - } else { - if (obj instanceof Map && ((Map) obj).containsKey(key)) { - return true; - } - - final int index = ArrayIndex.getArrayIndex(key); - if (index >= 0) { - if (obj instanceof List && index < ((List) obj).size()) { - return true; - } - if (obj.getClass().isArray() && index < Array.getLength(obj)) { - return true; - } - } - - return BeansLinker.getReadableInstancePropertyNames(obj.getClass()).contains(Objects.toString(key)) - || BeansLinker.getInstanceMethodNames(obj.getClass()).contains(Objects.toString(key)); - } - } - - 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; - } -}