blob: 9ca599c662907e313eba3ec0c52848bb83dc5ed8 [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, 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.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import jdk.internal.dynalink.beans.StaticClass;
import jdk.nashorn.api.scripting.AbstractJSObject;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.parser.Lexer;
import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
/**
* Representation for ECMAScript types - this maps directly to the ECMA script standard
*/
public enum JSType {
/** The undefined type */
UNDEFINED("undefined"),
/** The null type */
NULL("object"),
/** The boolean type */
BOOLEAN("boolean"),
/** The number type */
NUMBER("number"),
/** The string type */
STRING("string"),
/** The object type */
OBJECT("object"),
/** The function type */
FUNCTION("function");
/** The type name as returned by ECMAScript "typeof" operator*/
private final String typeName;
/** Max value for an uint32 in JavaScript */
public static final long MAX_UINT = 0xFFFF_FFFFL;
private static final MethodHandles.Lookup JSTYPE_LOOKUP = MethodHandles.lookup();
/** JavaScript compliant conversion function from Object to boolean */
public static final Call TO_BOOLEAN = staticCall(JSTYPE_LOOKUP, JSType.class, "toBoolean", boolean.class, Object.class);
/** JavaScript compliant conversion function from number to boolean */
public static final Call TO_BOOLEAN_D = staticCall(JSTYPE_LOOKUP, JSType.class, "toBoolean", boolean.class, double.class);
/** JavaScript compliant conversion function from Object to integer */
public static final Call TO_INTEGER = staticCall(JSTYPE_LOOKUP, JSType.class, "toInteger", int.class, Object.class);
/** JavaScript compliant conversion function from Object to long */
public static final Call TO_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "toLong", long.class, Object.class);
/** JavaScript compliant conversion function from double to long */
public static final Call TO_LONG_D = staticCall(JSTYPE_LOOKUP, JSType.class, "toLong", long.class, double.class);
/** JavaScript compliant conversion function from Object to number */
public static final Call TO_NUMBER = staticCall(JSTYPE_LOOKUP, JSType.class, "toNumber", double.class, Object.class);
/** JavaScript compliant conversion function from Object to number with type check */
public static final Call TO_NUMBER_OPTIMISTIC = staticCall(JSTYPE_LOOKUP, JSType.class, "toNumberOptimistic", double.class, Object.class, int.class);
/** JavaScript compliant conversion function from Object to String */
public static final Call TO_STRING = staticCall(JSTYPE_LOOKUP, JSType.class, "toString", String.class, Object.class);
/** JavaScript compliant conversion function from Object to int32 */
public static final Call TO_INT32 = staticCall(JSTYPE_LOOKUP, JSType.class, "toInt32", int.class, Object.class);
/** JavaScript compliant conversion function from Object to int32 */
public static final Call TO_INT32_L = staticCall(JSTYPE_LOOKUP, JSType.class, "toInt32", int.class, long.class);
/** JavaScript compliant conversion function from Object to int32 with type check */
public static final Call TO_INT32_OPTIMISTIC = staticCall(JSTYPE_LOOKUP, JSType.class, "toInt32Optimistic", int.class, Object.class, int.class);
/** JavaScript compliant conversion function from double to int32 */
public static final Call TO_INT32_D = staticCall(JSTYPE_LOOKUP, JSType.class, "toInt32", int.class, double.class);
/** JavaScript compliant conversion function from int to uint32 */
public static final Call TO_UINT32_OPTIMISTIC = staticCall(JSTYPE_LOOKUP, JSType.class, "toUint32Optimistic", int.class, int.class, int.class);
/** JavaScript compliant conversion function from int to uint32 */
public static final Call TO_UINT32_DOUBLE = staticCall(JSTYPE_LOOKUP, JSType.class, "toUint32Double", double.class, int.class);
/** JavaScript compliant conversion function from Object to uint32 */
public static final Call TO_UINT32 = staticCall(JSTYPE_LOOKUP, JSType.class, "toUint32", long.class, Object.class);
/** JavaScript compliant conversion function from number to uint32 */
public static final Call TO_UINT32_D = staticCall(JSTYPE_LOOKUP, JSType.class, "toUint32", long.class, double.class);
/** JavaScript compliant conversion function from number to String */
public static final Call TO_STRING_D = staticCall(JSTYPE_LOOKUP, JSType.class, "toString", String.class, double.class);
/** Combined call to toPrimitive followed by toString. */
public static final Call TO_PRIMITIVE_TO_STRING = staticCall(JSTYPE_LOOKUP, JSType.class, "toPrimitiveToString", String.class, Object.class);
/** Combined call to toPrimitive followed by toCharSequence. */
public static final Call TO_PRIMITIVE_TO_CHARSEQUENCE = staticCall(JSTYPE_LOOKUP, JSType.class, "toPrimitiveToCharSequence", CharSequence.class, Object.class);
/** Throw an unwarranted optimism exception */
public static final Call THROW_UNWARRANTED = staticCall(JSTYPE_LOOKUP, JSType.class, "throwUnwarrantedOptimismException", Object.class, Object.class, int.class);
/** Add exact wrapper for potentially overflowing integer operations */
public static final Call ADD_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "addExact", int.class, int.class, int.class, int.class);
/** Sub exact wrapper for potentially overflowing integer operations */
public static final Call SUB_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "subExact", int.class, int.class, int.class, int.class);
/** Multiply exact wrapper for potentially overflowing integer operations */
public static final Call MUL_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "mulExact", int.class, int.class, int.class, int.class);
/** Div exact wrapper for potentially integer division that turns into float point */
public static final Call DIV_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "divExact", int.class, int.class, int.class, int.class);
/** Div zero wrapper for integer division that handles (0/0)|0 == 0 */
public static final Call DIV_ZERO = staticCall(JSTYPE_LOOKUP, JSType.class, "divZero", int.class, int.class, int.class);
/** Mod zero wrapper for integer division that handles (0%0)|0 == 0 */
public static final Call REM_ZERO = staticCall(JSTYPE_LOOKUP, JSType.class, "remZero", int.class, int.class, int.class);
/** Mod exact wrapper for potentially integer remainders that turns into float point */
public static final Call REM_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "remExact", int.class, int.class, int.class, int.class);
/** Decrement exact wrapper for potentially overflowing integer operations */
public static final Call DECREMENT_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "decrementExact", int.class, int.class, int.class);
/** Increment exact wrapper for potentially overflowing integer operations */
public static final Call INCREMENT_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "incrementExact", int.class, int.class, int.class);
/** Negate exact exact wrapper for potentially overflowing integer operations */
public static final Call NEGATE_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "negateExact", int.class, int.class, int.class);
/** Method handle to convert a JS Object to a Java array. */
public static final Call TO_JAVA_ARRAY = staticCall(JSTYPE_LOOKUP, JSType.class, "toJavaArray", Object.class, Object.class, Class.class);
/** Method handle for void returns. */
public static final Call VOID_RETURN = staticCall(JSTYPE_LOOKUP, JSType.class, "voidReturn", void.class);
/** Method handle for isString method */
public static final Call IS_STRING = staticCall(JSTYPE_LOOKUP, JSType.class, "isString", boolean.class, Object.class);
/** Method handle for isNumber method */
public static final Call IS_NUMBER = staticCall(JSTYPE_LOOKUP, JSType.class, "isNumber", boolean.class, Object.class);
/**
* The list of available accessor types in width order. This order is used for type guesses narrow{@literal ->} wide
* in the dual--fields world
*/
private static final List<Type> ACCESSOR_TYPES = Collections.unmodifiableList(
Arrays.asList(
Type.INT,
Type.NUMBER,
Type.OBJECT));
/** table index for undefined type - hard coded so it can be used in switches at compile time */
public static final int TYPE_UNDEFINED_INDEX = -1;
/** table index for integer type - hard coded so it can be used in switches at compile time */
public static final int TYPE_INT_INDEX = 0; //getAccessorTypeIndex(int.class);
/** table index for double type - hard coded so it can be used in switches at compile time */
public static final int TYPE_DOUBLE_INDEX = 1; //getAccessorTypeIndex(double.class);
/** table index for object type - hard coded so it can be used in switches at compile time */
public static final int TYPE_OBJECT_INDEX = 2; //getAccessorTypeIndex(Object.class);
/** object conversion quickies with JS semantics - used for return value and parameter filter */
public static final List<MethodHandle> CONVERT_OBJECT = toUnmodifiableList(
JSType.TO_INT32.methodHandle(),
JSType.TO_NUMBER.methodHandle(),
null
);
/**
* object conversion quickies with JS semantics - used for return value and parameter filter, optimistic
* throws exception upon incompatible type (asking for a narrower one than the storage)
*/
public static final List<MethodHandle> CONVERT_OBJECT_OPTIMISTIC = toUnmodifiableList(
JSType.TO_INT32_OPTIMISTIC.methodHandle(),
JSType.TO_NUMBER_OPTIMISTIC.methodHandle(),
null
);
/** The value of Undefined cast to an int32 */
public static final int UNDEFINED_INT = 0;
/** The value of Undefined cast to a long */
public static final long UNDEFINED_LONG = 0L;
/** The value of Undefined cast to a double */
public static final double UNDEFINED_DOUBLE = Double.NaN;
// Minimum and maximum range between which every long value can be precisely represented as a double.
private static final long MAX_PRECISE_DOUBLE = 1L << 53;
private static final long MIN_PRECISE_DOUBLE = -MAX_PRECISE_DOUBLE;
/**
* Method handles for getters that return undefined coerced
* to the appropriate type
*/
public static final List<MethodHandle> GET_UNDEFINED = toUnmodifiableList(
MH.constant(int.class, UNDEFINED_INT),
MH.constant(double.class, UNDEFINED_DOUBLE),
MH.constant(Object.class, Undefined.getUndefined())
);
private static final double INT32_LIMIT = 4294967296.0;
/**
* Constructor
*
* @param typeName the type name
*/
private JSType(final String typeName) {
this.typeName = typeName;
}
/**
* The external type name as returned by ECMAScript "typeof" operator
*
* @return type name for this type
*/
public final String typeName() {
return this.typeName;
}
/**
* Return the JSType for a given object
*
* @param obj an object
*
* @return the JSType for the object
*/
public static JSType of(final Object obj) {
// Order of these statements is tuned for performance (see JDK-8024476)
if (obj == null) {
return JSType.NULL;
}
if (obj instanceof ScriptObject) {
return obj instanceof ScriptFunction ? JSType.FUNCTION : JSType.OBJECT;
}
if (obj instanceof Boolean) {
return JSType.BOOLEAN;
}
if (isString(obj)) {
return JSType.STRING;
}
if (isNumber(obj)) {
return JSType.NUMBER;
}
if (obj == ScriptRuntime.UNDEFINED) {
return JSType.UNDEFINED;
}
return Bootstrap.isCallable(obj) ? JSType.FUNCTION : JSType.OBJECT;
}
/**
* Similar to {@link #of(Object)}, but does not distinguish between {@link #FUNCTION} and {@link #OBJECT}, returning
* {@link #OBJECT} in both cases. The distinction is costly, and the EQ and STRICT_EQ predicates don't care about it
* so we maintain this version for their use.
*
* @param obj an object
*
* @return the JSType for the object; returns {@link #OBJECT} instead of {@link #FUNCTION} for functions.
*/
public static JSType ofNoFunction(final Object obj) {
// Order of these statements is tuned for performance (see JDK-8024476)
if (obj == null) {
return JSType.NULL;
}
if (obj instanceof ScriptObject) {
return JSType.OBJECT;
}
if (obj instanceof Boolean) {
return JSType.BOOLEAN;
}
if (isString(obj)) {
return JSType.STRING;
}
if (isNumber(obj)) {
return JSType.NUMBER;
}
if (obj == ScriptRuntime.UNDEFINED) {
return JSType.UNDEFINED;
}
return JSType.OBJECT;
}
/**
* Void return method handle glue
*/
public static void voidReturn() {
//empty
//TODO: fix up SetMethodCreator better so we don't need this stupid thing
}
/**
* Returns true if double number can be represented as an int
*
* @param number a long to inspect
*
* @return true for int representable longs
*/
public static boolean isRepresentableAsInt(final long number) {
return (int)number == number;
}
/**
* Returns true if double number can be represented as an int. Note that it returns true for negative
* zero. If you need to exclude negative zero, use {@link #isStrictlyRepresentableAsInt(double)}.
*
* @param number a double to inspect
*
* @return true for int representable doubles
*/
public static boolean isRepresentableAsInt(final double number) {
return (int)number == number;
}
/**
* Returns true if double number can be represented as an int. Note that it returns false for negative
* zero. If you don't need to distinguish negative zero, use {@link #isRepresentableAsInt(double)}.
*
* @param number a double to inspect
*
* @return true for int representable doubles
*/
public static boolean isStrictlyRepresentableAsInt(final double number) {
return isRepresentableAsInt(number) && isNotNegativeZero(number);
}
/**
* Returns true if Object can be represented as an int
*
* @param obj an object to inspect
*
* @return true for int representable objects
*/
public static boolean isRepresentableAsInt(final Object obj) {
if (obj instanceof Number) {
return isRepresentableAsInt(((Number)obj).doubleValue());
}
return false;
}
/**
* Returns true if double number can be represented as a long. Note that it returns true for negative
* zero.
*
* @param number a double to inspect
* @return true for long representable doubles
*/
public static boolean isRepresentableAsLong(final double number) {
return (long)number == number;
}
/**
* Returns true if long number can be represented as double without loss of precision.
* @param number a long number
* @return true if the double representation does not lose precision
*/
public static boolean isRepresentableAsDouble(final long number) {
return MAX_PRECISE_DOUBLE >= number && number >= MIN_PRECISE_DOUBLE;
}
/**
* Returns true if the number is not the negative zero ({@code -0.0d}).
* @param number the number to test
* @return true if it is not the negative zero, false otherwise.
*/
private static boolean isNotNegativeZero(final double number) {
return Double.doubleToRawLongBits(number) != 0x8000000000000000L;
}
/**
* Check whether an object is primitive
*
* @param obj an object
*
* @return true if object is primitive (includes null and undefined)
*/
public static boolean isPrimitive(final Object obj) {
return obj == null ||
obj == ScriptRuntime.UNDEFINED ||
isString(obj) ||
isNumber(obj) ||
obj instanceof Boolean;
}
/**
* Primitive converter for an object
*
* @param obj an object
*
* @return primitive form of the object
*/
public static Object toPrimitive(final Object obj) {
return toPrimitive(obj, null);
}
/**
* Primitive converter for an object including type hint
* See ECMA 9.1 ToPrimitive
*
* @param obj an object
* @param hint a type hint
*
* @return the primitive form of the object
*/
public static Object toPrimitive(final Object obj, final Class<?> hint) {
if (obj instanceof ScriptObject) {
return toPrimitive((ScriptObject)obj, hint);
} else if (isPrimitive(obj)) {
return obj;
} else if (obj instanceof JSObject) {
return toPrimitive((JSObject)obj, hint);
} else if (obj instanceof StaticClass) {
final String name = ((StaticClass)obj).getRepresentedClass().getName();
return new StringBuilder(12 + name.length()).append("[JavaClass ").append(name).append(']').toString();
}
return obj.toString();
}
private static Object toPrimitive(final ScriptObject sobj, final Class<?> hint) {
return requirePrimitive(sobj.getDefaultValue(hint));
}
private static Object requirePrimitive(final Object result) {
if (!isPrimitive(result)) {
throw typeError("bad.default.value", result.toString());
}
return result;
}
/**
* Primitive converter for a {@link JSObject} including type hint. Invokes
* {@link AbstractJSObject#getDefaultValue(JSObject, Class)} and translates any thrown
* {@link UnsupportedOperationException} to an ECMAScript {@code TypeError}.
* See ECMA 9.1 ToPrimitive
*
* @param jsobj a JSObject
* @param hint a type hint
*
* @return the primitive form of the JSObject
*/
public static Object toPrimitive(final JSObject jsobj, final Class<?> hint) {
try {
return requirePrimitive(AbstractJSObject.getDefaultValue(jsobj, hint));
} catch (final UnsupportedOperationException e) {
throw new ECMAException(Context.getGlobal().newTypeError(e.getMessage()), e);
}
}
/**
* Combines a hintless toPrimitive and a toString call.
*
* @param obj an object
*
* @return the string form of the primitive form of the object
*/
public static String toPrimitiveToString(final Object obj) {
return toString(toPrimitive(obj));
}
/**
* Like {@link #toPrimitiveToString(Object)}, but avoids conversion of ConsString to String.
*
* @param obj an object
* @return the CharSequence form of the primitive form of the object
*/
public static CharSequence toPrimitiveToCharSequence(final Object obj) {
return toCharSequence(toPrimitive(obj));
}
/**
* JavaScript compliant conversion of number to boolean
*
* @param num a number
*
* @return a boolean
*/
public static boolean toBoolean(final double num) {
return num != 0 && !Double.isNaN(num);
}
/**
* JavaScript compliant conversion of Object to boolean
* See ECMA 9.2 ToBoolean
*
* @param obj an object
*
* @return a boolean
*/
public static boolean toBoolean(final Object obj) {
if (obj instanceof Boolean) {
return (Boolean)obj;
}
if (nullOrUndefined(obj)) {
return false;
}
if (obj instanceof Number) {
final double num = ((Number)obj).doubleValue();
return num != 0 && !Double.isNaN(num);
}
if (isString(obj)) {
return ((CharSequence)obj).length() > 0;
}
return true;
}
/**
* JavaScript compliant converter of Object to String
* See ECMA 9.8 ToString
*
* @param obj an object
*
* @return a string
*/
public static String toString(final Object obj) {
return toStringImpl(obj, false);
}
/**
* If obj is an instance of {@link ConsString} cast to CharSequence, else return
* result of {@link #toString(Object)}.
*
* @param obj an object
* @return an instance of String or ConsString
*/
public static CharSequence toCharSequence(final Object obj) {
if (obj instanceof ConsString) {
return (CharSequence) obj;
}
return toString(obj);
}
/**
* Returns true if object represents a primitive JavaScript string value.
* @param obj the object
* @return true if the object represents a primitive JavaScript string value.
*/
public static boolean isString(final Object obj) {
return obj instanceof String || obj instanceof ConsString;
}
/**
* Returns true if object represents a primitive JavaScript number value. Note that we only
* treat wrapper objects of Java primitive number types as objects that can be fully represented
* as JavaScript numbers (doubles). This means we exclude {@code long} and special purpose Number
* instances such as {@link java.util.concurrent.atomic.AtomicInteger}, as well as arbitrary precision
* numbers such as {@link java.math.BigInteger}.
*
* @param obj the object
* @return true if the object represents a primitive JavaScript number value.
*/
public static boolean isNumber(final Object obj) {
if (obj != null) {
final Class<?> c = obj.getClass();
return c == Integer.class || c == Double.class || c == Float.class || c == Short.class || c == Byte.class;
}
return false;
}
/**
* JavaScript compliant conversion of integer to String
*
* @param num an integer
*
* @return a string
*/
public static String toString(final int num) {
return Integer.toString(num);
}
/**
* JavaScript compliant conversion of number to String
* See ECMA 9.8.1
*
* @param num a number
*
* @return a string
*/
public static String toString(final double num) {
if (isRepresentableAsInt(num)) {
return Integer.toString((int)num);
}
if (num == Double.POSITIVE_INFINITY) {
return "Infinity";
}
if (num == Double.NEGATIVE_INFINITY) {
return "-Infinity";
}
if (Double.isNaN(num)) {
return "NaN";
}
return NumberToString.stringFor(num);
}
/**
* JavaScript compliant conversion of number to String
*
* @param num a number
* @param radix a radix for the conversion
*
* @return a string
*/
public static String toString(final double num, final int radix) {
assert radix >= 2 && radix <= 36 : "invalid radix";
if (isRepresentableAsInt(num)) {
return Integer.toString((int)num, radix);
}
if (num == Double.POSITIVE_INFINITY) {
return "Infinity";
}
if (num == Double.NEGATIVE_INFINITY) {
return "-Infinity";
}
if (Double.isNaN(num)) {
return "NaN";
}
if (num == 0.0) {
return "0";
}
final String chars = "0123456789abcdefghijklmnopqrstuvwxyz";
final StringBuilder sb = new StringBuilder();
final boolean negative = num < 0.0;
final double signedNum = negative ? -num : num;
double intPart = Math.floor(signedNum);
double decPart = signedNum - intPart;
// encode integer part from least significant digit, then reverse
do {
final double remainder = intPart % radix;
sb.append(chars.charAt((int) remainder));
intPart -= remainder;
intPart /= radix;
} while (intPart >= 1.0);
if (negative) {
sb.append('-');
}
sb.reverse();
// encode decimal part
if (decPart > 0.0) {
final int dot = sb.length();
sb.append('.');
do {
decPart *= radix;
final double d = Math.floor(decPart);
sb.append(chars.charAt((int)d));
decPart -= d;
} while (decPart > 0.0 && sb.length() - dot < 1100);
// somewhat arbitrarily use same limit as V8
}
return sb.toString();
}
/**
* JavaScript compliant conversion of Object to number
* See ECMA 9.3 ToNumber
*
* @param obj an object
*
* @return a number
*/
public static double toNumber(final Object obj) {
if (obj instanceof Double) {
return (Double)obj;
}
if (obj instanceof Number) {
return ((Number)obj).doubleValue();
}
return toNumberGeneric(obj);
}
/**
* Converts an object for a comparison with a number. Almost identical to {@link #toNumber(Object)} but
* converts {@code null} to {@code NaN} instead of zero, so it won't compare equal to zero.
*
* @param obj an object
*
* @return a number
*/
public static double toNumberForEq(final Object obj) {
return obj == null ? Double.NaN : toNumber(obj);
}
/**
* Converts an object for strict comparison with a number. Returns {@code NaN} for any object that is not
* a {@link Number}, so only boxed numerics can compare strictly equal to numbers.
*
* @param obj an object
*
* @return a number
*/
public static double toNumberForStrictEq(final Object obj) {
if (obj instanceof Double) {
return (Double)obj;
}
if (isNumber(obj)) {
return ((Number)obj).doubleValue();
}
return Double.NaN;
}
/**
* Convert a long to the narrowest JavaScript Number type. This returns either a
* {@link Integer} or {@link Double} depending on the magnitude of {@code l}.
* @param l a long value
* @return the value converted to Integer or Double
*/
public static Number toNarrowestNumber(final long l) {
return isRepresentableAsInt(l) ? Integer.valueOf((int) l) : Double.valueOf((double) l);
}
/**
* JavaScript compliant conversion of Boolean to number
* See ECMA 9.3 ToNumber
*
* @param b a boolean
*
* @return JS numeric value of the boolean: 1.0 or 0.0
*/
public static double toNumber(final Boolean b) {
return b ? 1d : +0d;
}
/**
* JavaScript compliant conversion of Object to number
* See ECMA 9.3 ToNumber
*
* @param obj an object
*
* @return a number
*/
public static double toNumber(final ScriptObject obj) {
return toNumber(toPrimitive(obj, Number.class));
}
/**
* Optimistic number conversion - throws UnwarrantedOptimismException if Object
*
* @param obj object to convert
* @param programPoint program point
* @return double
*/
public static double toNumberOptimistic(final Object obj, final int programPoint) {
if (obj != null) {
final Class<?> clz = obj.getClass();
if (clz == Double.class || clz == Integer.class || clz == Long.class) {
return ((Number)obj).doubleValue();
}
}
throw new UnwarrantedOptimismException(obj, programPoint);
}
/**
* Object to number conversion that delegates to either {@link #toNumber(Object)} or to
* {@link #toNumberOptimistic(Object, int)} depending on whether the program point is valid or not.
* @param obj the object to convert
* @param programPoint the program point; can be invalid.
* @return the value converted to a number
* @throws UnwarrantedOptimismException if the value can't be represented as a number and the program point is valid.
*/
public static double toNumberMaybeOptimistic(final Object obj, final int programPoint) {
return UnwarrantedOptimismException.isValid(programPoint) ? toNumberOptimistic(obj, programPoint) : toNumber(obj);
}
/**
* Digit representation for a character
*
* @param ch a character
* @param radix radix
*
* @return the digit for this character
*/
public static int digit(final char ch, final int radix) {
return digit(ch, radix, false);
}
/**
* Digit representation for a character
*
* @param ch a character
* @param radix radix
* @param onlyIsoLatin1 iso latin conversion only
*
* @return the digit for this character
*/
public static int digit(final char ch, final int radix, final boolean onlyIsoLatin1) {
final char maxInRadix = (char)('a' + (radix - 1) - 10);
final char c = Character.toLowerCase(ch);
if (c >= 'a' && c <= maxInRadix) {
return Character.digit(ch, radix);
}
if (Character.isDigit(ch)) {
if (!onlyIsoLatin1 || ch >= '0' && ch <= '9') {
return Character.digit(ch, radix);
}
}
return -1;
}
/**
* JavaScript compliant String to number conversion
*
* @param str a string
*
* @return a number
*/
public static double toNumber(final String str) {
int end = str.length();
if (end == 0) {
return 0.0; // Empty string
}
int start = 0;
char f = str.charAt(0);
while (Lexer.isJSWhitespace(f)) {
if (++start == end) {
return 0.0d; // All whitespace string
}
f = str.charAt(start);
}
// Guaranteed to terminate even without start >= end check, as the previous loop found at least one
// non-whitespace character.
while (Lexer.isJSWhitespace(str.charAt(end - 1))) {
end--;
}
final boolean negative;
if (f == '-') {
if(++start == end) {
return Double.NaN; // Single-char "-" string
}
f = str.charAt(start);
negative = true;
} else {
if (f == '+') {
if (++start == end) {
return Double.NaN; // Single-char "+" string
}
f = str.charAt(start);
}
negative = false;
}
final double value;
if (start + 1 < end && f == '0' && Character.toLowerCase(str.charAt(start + 1)) == 'x') {
//decode hex string
value = parseRadix(str.toCharArray(), start + 2, end, 16);
} else if (f == 'I' && end - start == 8 && str.regionMatches(start, "Infinity", 0, 8)) {
return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
} else {
// Fast (no NumberFormatException) path to NaN for non-numeric strings.
for (int i = start; i < end; i++) {
f = str.charAt(i);
if ((f < '0' || f > '9') && f != '.' && f != 'e' && f != 'E' && f != '+' && f != '-') {
return Double.NaN;
}
}
try {
value = Double.parseDouble(str.substring(start, end));
} catch (final NumberFormatException e) {
return Double.NaN;
}
}
return negative ? -value : value;
}
/**
* JavaScript compliant Object to integer conversion. See ECMA 9.4 ToInteger
*
* <p>Note that this returns {@link java.lang.Integer#MAX_VALUE} or {@link java.lang.Integer#MIN_VALUE}
* for double values that exceed the int range, including positive and negative Infinity. It is the
* caller's responsibility to handle such values correctly.</p>
*
* @param obj an object
* @return an integer
*/
public static int toInteger(final Object obj) {
return (int)toNumber(obj);
}
/**
* Converts an Object to long.
*
* <p>Note that this returns {@link java.lang.Long#MAX_VALUE} or {@link java.lang.Long#MIN_VALUE}
* for double values that exceed the long range, including positive and negative Infinity. It is the
* caller's responsibility to handle such values correctly.</p>
*
* @param obj an object
* @return a long
*/
public static long toLong(final Object obj) {
return obj instanceof Long ? ((Long)obj).longValue() : toLong(toNumber(obj));
}
/**
* Converts a double to long.
*
* @param num the double to convert
* @return the converted long value
*/
public static long toLong(final double num) {
return (long)num;
}
/**
* JavaScript compliant Object to int32 conversion
* See ECMA 9.5 ToInt32
*
* @param obj an object
* @return an int32
*/
public static int toInt32(final Object obj) {
return toInt32(toNumber(obj));
}
/**
* Optimistic int conversion - throws UnwarrantedOptimismException if double, long or Object
*
* @param obj object to convert
* @param programPoint program point
* @return double
*/
public static int toInt32Optimistic(final Object obj, final int programPoint) {
if (obj != null && obj.getClass() == Integer.class) {
return ((Integer)obj).intValue();
}
throw new UnwarrantedOptimismException(obj, programPoint);
}
/**
* Object to int conversion that delegates to either {@link #toInt32(Object)} or to
* {@link #toInt32Optimistic(Object, int)} depending on whether the program point is valid or not.
* @param obj the object to convert
* @param programPoint the program point; can be invalid.
* @return the value converted to int
* @throws UnwarrantedOptimismException if the value can't be represented as int and the program point is valid.
*/
public static int toInt32MaybeOptimistic(final Object obj, final int programPoint) {
return UnwarrantedOptimismException.isValid(programPoint) ? toInt32Optimistic(obj, programPoint) : toInt32(obj);
}
/**
* JavaScript compliant long to int32 conversion
*
* @param num a long
* @return an int32
*/
public static int toInt32(final long num) {
return (int)(num >= MIN_PRECISE_DOUBLE && num <= MAX_PRECISE_DOUBLE ? num : (long)(num % INT32_LIMIT));
}
/**
* JavaScript compliant number to int32 conversion
*
* @param num a number
* @return an int32
*/
public static int toInt32(final double num) {
return (int)doubleToInt32(num);
}
/**
* JavaScript compliant Object to uint32 conversion
*
* @param obj an object
* @return a uint32
*/
public static long toUint32(final Object obj) {
return toUint32(toNumber(obj));
}
/**
* JavaScript compliant number to uint32 conversion
*
* @param num a number
* @return a uint32
*/
public static long toUint32(final double num) {
return doubleToInt32(num) & MAX_UINT;
}
/**
* JavaScript compliant int to uint32 conversion
*
* @param num an int
* @return a uint32
*/
public static long toUint32(final int num) {
return num & MAX_UINT;
}
/**
* Optimistic JavaScript compliant int to uint32 conversion
* @param num an int
* @param pp the program point
* @return the uint32 value if it can be represented by an int
* @throws UnwarrantedOptimismException if uint32 value cannot be represented by an int
*/
public static int toUint32Optimistic(final int num, final int pp) {
if (num >= 0) {
return num;
}
throw new UnwarrantedOptimismException(toUint32Double(num), pp, Type.NUMBER);
}
/**
* JavaScript compliant int to uint32 conversion with double return type
* @param num an int
* @return the uint32 value as double
*/
public static double toUint32Double(final int num) {
return (double) toUint32(num);
}
/**
* JavaScript compliant Object to uint16 conversion
* ECMA 9.7 ToUint16: (Unsigned 16 Bit Integer)
*
* @param obj an object
* @return a uint16
*/
public static int toUint16(final Object obj) {
return toUint16(toNumber(obj));
}
/**
* JavaScript compliant number to uint16 conversion
*
* @param num a number
* @return a uint16
*/
public static int toUint16(final int num) {
return num & 0xffff;
}
/**
* JavaScript compliant number to uint16 conversion
*
* @param num a number
* @return a uint16
*/
public static int toUint16(final long num) {
return (int)num & 0xffff;
}
/**
* JavaScript compliant number to uint16 conversion
*
* @param num a number
* @return a uint16
*/
public static int toUint16(final double num) {
return (int)doubleToInt32(num) & 0xffff;
}
private static long doubleToInt32(final double num) {
final int exponent = Math.getExponent(num);
if (exponent < 31) {
return (long) num; // Fits into 32 bits
}
if (exponent >= 84) {
// Either infinite or NaN or so large that shift / modulo will produce 0
// (52 bit mantissa + 32 bit target width).
return 0;
}
// This is rather slow and could probably be sped up using bit-fiddling.
final double d = num >= 0 ? Math.floor(num) : Math.ceil(num);
return (long)(d % INT32_LIMIT);
}
/**
* Check whether a number is finite
*
* @param num a number
* @return true if finite
*/
public static boolean isFinite(final double num) {
return !Double.isInfinite(num) && !Double.isNaN(num);
}
/**
* Convert a primitive to a double
*
* @param num a double
* @return a boxed double
*/
public static Double toDouble(final double num) {
return num;
}
/**
* Convert a primitive to a double
*
* @param num a long
* @return a boxed double
*/
public static Double toDouble(final long num) {
return (double)num;
}
/**
* Convert a primitive to a double
*
* @param num an int
* @return a boxed double
*/
public static Double toDouble(final int num) {
return (double)num;
}
/**
* Convert a boolean to an Object
*
* @param bool a boolean
* @return a boxed boolean, its Object representation
*/
public static Object toObject(final boolean bool) {
return bool;
}
/**
* Convert a number to an Object
*
* @param num an integer
* @return the boxed number
*/
public static Object toObject(final int num) {
return num;
}
/**
* Convert a number to an Object
*
* @param num a long
* @return the boxed number
*/
public static Object toObject(final long num) {
return num;
}
/**
* Convert a number to an Object
*
* @param num a double
* @return the boxed number
*/
public static Object toObject(final double num) {
return num;
}
/**
* Identity converter for objects.
*
* @param obj an object
* @return the boxed number
*/
public static Object toObject(final Object obj) {
return obj;
}
/**
* Object conversion. This is used to convert objects and numbers to their corresponding
* NativeObject type
* See ECMA 9.9 ToObject
*
* @param obj the object to convert
*
* @return the wrapped object
*/
public static Object toScriptObject(final Object obj) {
return toScriptObject(Context.getGlobal(), obj);
}
/**
* Object conversion. This is used to convert objects and numbers to their corresponding
* NativeObject type
* See ECMA 9.9 ToObject
*
* @param global the global object
* @param obj the object to convert
*
* @return the wrapped object
*/
public static Object toScriptObject(final Global global, final Object obj) {
if (nullOrUndefined(obj)) {
throw typeError(global, "not.an.object", ScriptRuntime.safeToString(obj));
}
if (obj instanceof ScriptObject) {
return obj;
}
return global.wrapAsObject(obj);
}
/**
* Script object to Java array conversion.
*
* @param obj script object to be converted to Java array
* @param componentType component type of the destination array required
* @return converted Java array
*/
public static Object toJavaArray(final Object obj, final Class<?> componentType) {
if (obj instanceof ScriptObject) {
return ((ScriptObject)obj).getArray().asArrayOfType(componentType);
} else if (obj instanceof JSObject) {
final ArrayLikeIterator<?> itr = ArrayLikeIterator.arrayLikeIterator(obj);
final int len = (int) itr.getLength();
final Object[] res = new Object[len];
int idx = 0;
while (itr.hasNext()) {
res[idx++] = itr.next();
}
return convertArray(res, componentType);
} else if(obj == null) {
return null;
} else {
throw new IllegalArgumentException("not a script object");
}
}
/**
* Java array to java array conversion - but using type conversions implemented by linker.
*
* @param src source array
* @param componentType component type of the destination array required
* @return converted Java array
*/
public static Object convertArray(final Object[] src, final Class<?> componentType) {
if(componentType == Object.class) {
for(int i = 0; i < src.length; ++i) {
final Object e = src[i];
if(e instanceof ConsString) {
src[i] = e.toString();
}
}
}
final int l = src.length;
final Object dst = Array.newInstance(componentType, l);
final MethodHandle converter = Bootstrap.getLinkerServices().getTypeConverter(Object.class, componentType);
try {
for (int i = 0; i < src.length; i++) {
Array.set(dst, i, invoke(converter, src[i]));
}
} catch (final RuntimeException | Error e) {
throw e;
} catch (final Throwable t) {
throw new RuntimeException(t);
}
return dst;
}
/**
* Check if an object is null or undefined
*
* @param obj object to check
*
* @return true if null or undefined
*/
public static boolean nullOrUndefined(final Object obj) {
return obj == null || obj == ScriptRuntime.UNDEFINED;
}
static String toStringImpl(final Object obj, final boolean safe) {
if (obj instanceof String) {
return (String)obj;
}
if (obj instanceof ConsString) {
return obj.toString();
}
if (isNumber(obj)) {
return toString(((Number)obj).doubleValue());
}
if (obj == ScriptRuntime.UNDEFINED) {
return "undefined";
}
if (obj == null) {
return "null";
}
if (obj instanceof Boolean) {
return obj.toString();
}
if (safe && obj instanceof ScriptObject) {
final ScriptObject sobj = (ScriptObject)obj;
final Global gobj = Context.getGlobal();
return gobj.isError(sobj) ?
ECMAException.safeToString(sobj) :
sobj.safeToString();
}
return toString(toPrimitive(obj, String.class));
}
// trim from left for JS whitespaces.
static String trimLeft(final String str) {
int start = 0;
while (start < str.length() && Lexer.isJSWhitespace(str.charAt(start))) {
start++;
}
return str.substring(start);
}
/**
* Throw an unwarranted optimism exception for a program point
* @param value real return value
* @param programPoint program point
* @return
*/
@SuppressWarnings("unused")
private static Object throwUnwarrantedOptimismException(final Object value, final int programPoint) {
throw new UnwarrantedOptimismException(value, programPoint);
}
/**
* Wrapper for addExact
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
* containing the result and the program point of the failure
*
* @param x first term
* @param y second term
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if overflow occurs
*/
public static int addExact(final int x, final int y, final int programPoint) throws UnwarrantedOptimismException {
try {
return Math.addExact(x, y);
} catch (final ArithmeticException e) {
throw new UnwarrantedOptimismException((double)x + (double)y, programPoint);
}
}
/**
* Wrapper for subExact
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
* containing the result and the program point of the failure
*
* @param x first term
* @param y second term
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if overflow occurs
*/
public static int subExact(final int x, final int y, final int programPoint) throws UnwarrantedOptimismException {
try {
return Math.subtractExact(x, y);
} catch (final ArithmeticException e) {
throw new UnwarrantedOptimismException((double)x - (double)y, programPoint);
}
}
/**
* Wrapper for mulExact
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
* containing the result and the program point of the failure
*
* @param x first term
* @param y second term
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if overflow occurs
*/
public static int mulExact(final int x, final int y, final int programPoint) throws UnwarrantedOptimismException {
try {
return Math.multiplyExact(x, y);
} catch (final ArithmeticException e) {
throw new UnwarrantedOptimismException((double)x * (double)y, programPoint);
}
}
/**
* Wrapper for divExact. Throws UnwarrantedOptimismException if the result of the division can't be represented as
* int.
*
* @param x first term
* @param y second term
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if the result of the division can't be represented as int.
*/
public static int divExact(final int x, final int y, final int programPoint) throws UnwarrantedOptimismException {
final int res;
try {
res = x / y;
} catch (final ArithmeticException e) {
assert y == 0; // Only div by zero anticipated
throw new UnwarrantedOptimismException(x > 0 ? Double.POSITIVE_INFINITY : x < 0 ? Double.NEGATIVE_INFINITY : Double.NaN, programPoint);
}
final int rem = x % y;
if (rem == 0) {
return res;
}
// go directly to double here, as anything with non zero remainder is a floating point number in JavaScript
throw new UnwarrantedOptimismException((double)x / (double)y, programPoint);
}
/**
* Implements int division but allows {@code x / 0} to be represented as 0. Basically equivalent to
* {@code (x / y)|0} JavaScript expression (division of two ints coerced to int).
* @param x the dividend
* @param y the divisor
* @return the result
*/
public static int divZero(final int x, final int y) {
return y == 0 ? 0 : x / y;
}
/**
* Implements int remainder but allows {@code x % 0} to be represented as 0. Basically equivalent to
* {@code (x % y)|0} JavaScript expression (remainder of two ints coerced to int).
* @param x the dividend
* @param y the divisor
* @return the remainder
*/
public static int remZero(final int x, final int y) {
return y == 0 ? 0 : x % y;
}
/**
* Wrapper for modExact. Throws UnwarrantedOptimismException if the modulo can't be represented as int.
*
* @param x first term
* @param y second term
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if the modulo can't be represented as int.
*/
public static int remExact(final int x, final int y, final int programPoint) throws UnwarrantedOptimismException {
try {
return x % y;
} catch (final ArithmeticException e) {
assert y == 0; // Only mod by zero anticipated
throw new UnwarrantedOptimismException(Double.NaN, programPoint);
}
}
/**
* Wrapper for decrementExact
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
* containing the result and the program point of the failure
*
* @param x number to negate
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if overflow occurs
*/
public static int decrementExact(final int x, final int programPoint) throws UnwarrantedOptimismException {
try {
return Math.decrementExact(x);
} catch (final ArithmeticException e) {
throw new UnwarrantedOptimismException((double)x - 1, programPoint);
}
}
/**
* Wrapper for incrementExact
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
* containing the result and the program point of the failure
*
* @param x the number to increment
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if overflow occurs
*/
public static int incrementExact(final int x, final int programPoint) throws UnwarrantedOptimismException {
try {
return Math.incrementExact(x);
} catch (final ArithmeticException e) {
throw new UnwarrantedOptimismException((double)x + 1, programPoint);
}
}
/**
* Wrapper for negateExact
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
* containing the result and the program point of the failure
*
* @param x the number to negate
* @param programPoint program point id
* @return the result
* @throws UnwarrantedOptimismException if overflow occurs
*/
public static int negateExact(final int x, final int programPoint) throws UnwarrantedOptimismException {
try {
if (x == 0) {
throw new UnwarrantedOptimismException(-0.0, programPoint);
}
return Math.negateExact(x);
} catch (final ArithmeticException e) {
throw new UnwarrantedOptimismException(-(double)x, programPoint);
}
}
/**
* Given a type of an accessor, return its index in [0..getNumberOfAccessorTypes())
*
* @param type the type
*
* @return the accessor index, or -1 if no accessor of this type exists
*/
public static int getAccessorTypeIndex(final Type type) {
return getAccessorTypeIndex(type.getTypeClass());
}
/**
* Given a class of an accessor, return its index in [0..getNumberOfAccessorTypes())
*
* Note that this is hardcoded with respect to the dynamic contents of the accessor
* types array for speed. Hotspot got stuck with this as 5% of the runtime in
* a benchmark when it looped over values and increased an index counter. :-(
*
* @param type the type
*
* @return the accessor index, or -1 if no accessor of this type exists
*/
public static int getAccessorTypeIndex(final Class<?> type) {
if (type == null) {
return TYPE_UNDEFINED_INDEX;
} else if (type == int.class) {
return TYPE_INT_INDEX;
} else if (type == double.class) {
return TYPE_DOUBLE_INDEX;
} else if (!type.isPrimitive()) {
return TYPE_OBJECT_INDEX;
}
return -1;
}
/**
* Return the accessor type based on its index in [0..getNumberOfAccessorTypes())
* Indexes are ordered narrower{@literal ->}wider / optimistic{@literal ->}pessimistic. Invalidations always
* go to a type of higher index
*
* @param index accessor type index
*
* @return a type corresponding to the index.
*/
public static Type getAccessorType(final int index) {
return ACCESSOR_TYPES.get(index);
}
/**
* Return the number of accessor types available.
*
* @return number of accessor types in system
*/
public static int getNumberOfAccessorTypes() {
return ACCESSOR_TYPES.size();
}
private static double parseRadix(final char chars[], final int start, final int length, final int radix) {
int pos = 0;
for (int i = start; i < length ; i++) {
if (digit(chars[i], radix) == -1) {
return Double.NaN;
}
pos++;
}
if (pos == 0) {
return Double.NaN;
}
double value = 0.0;
for (int i = start; i < start + pos; i++) {
value *= radix;
value += digit(chars[i], radix);
}
return value;
}
private static double toNumberGeneric(final Object obj) {
if (obj == null) {
return +0.0;
}
if (obj instanceof String) {
return toNumber((String)obj);
}
if (obj instanceof ConsString) {
return toNumber(obj.toString());
}
if (obj instanceof Boolean) {
return toNumber((Boolean)obj);
}
if (obj instanceof ScriptObject) {
return toNumber((ScriptObject)obj);
}
if (obj instanceof Undefined) {
return Double.NaN;
}
return toNumber(toPrimitive(obj, Number.class));
}
private static Object invoke(final MethodHandle mh, final Object arg) {
try {
return mh.invoke(arg);
} catch (final RuntimeException | Error e) {
throw e;
} catch (final Throwable t) {
throw new RuntimeException(t);
}
}
/**
* Create a method handle constant of the correct primitive type
* for a constant object
* @param o object
* @return constant function that returns object
*/
public static MethodHandle unboxConstant(final Object o) {
if (o != null) {
if (o.getClass() == Integer.class) {
return MH.constant(int.class, ((Integer)o).intValue());
} else if (o.getClass() == Double.class) {
return MH.constant(double.class, ((Double)o).doubleValue());
}
}
return MH.constant(Object.class, o);
}
/**
* Get the unboxed (primitive) type for an object
* @param o object
* @return primitive type or Object.class if not primitive
*/
public static Class<?> unboxedFieldType(final Object o) {
if (o == null) {
return Object.class;
} else if (o.getClass() == Integer.class) {
return int.class;
} else if (o.getClass() == Double.class) {
return double.class;
} else {
return Object.class;
}
}
private static final List<MethodHandle> toUnmodifiableList(final MethodHandle... methodHandles) {
return Collections.unmodifiableList(Arrays.asList(methodHandles));
}
}