8072426: Can't compare Java objects to strings or numbers
Reviewed-by: hannesw, lagergren, sundar
This commit is contained in:
parent
f3a755b2ab
commit
0a6d13699b
nashorn
make/nbproject
src/jdk.scripting.nashorn/share/classes/jdk/nashorn
api/scripting
internal/runtime
test/script/basic
@ -160,16 +160,16 @@
|
||||
<package-root>../test/src</package-root>
|
||||
<unit-tests/>
|
||||
<classpath mode="compile">../test/lib/testng.jar:../build/classes:../src/jdk.scripting.nashorn/share/classes</classpath>
|
||||
<source-level>1.7</source-level>
|
||||
<source-level>1.8</source-level>
|
||||
</compilation-unit>
|
||||
<compilation-unit>
|
||||
<package-root>../buildtools/nasgen/src</package-root>
|
||||
<classpath mode="compile">../build/classes:../src</classpath>
|
||||
<source-level>1.7</source-level>
|
||||
<source-level>1.8</source-level>
|
||||
</compilation-unit>
|
||||
<compilation-unit>
|
||||
<package-root>../src/jdk.scripting.nashorn/share/classes</package-root>
|
||||
<source-level>1.7</source-level>
|
||||
<source-level>1.8</source-level>
|
||||
</compilation-unit>
|
||||
</java-data>
|
||||
</configuration>
|
||||
|
55
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/DefaultValueImpl.java
Normal file
55
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/DefaultValueImpl.java
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 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.api.scripting;
|
||||
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link JSObject#getDefaultValue(Class)}. Isolated into a separate class mostly so
|
||||
* that we can have private static instances of function name arrays, something we couldn't declare without it
|
||||
* being visible in {@link JSObject} interface.
|
||||
*/
|
||||
class DefaultValueImpl {
|
||||
private static final String[] DEFAULT_VALUE_FNS_NUMBER = new String[] { "valueOf", "toString" };
|
||||
private static final String[] DEFAULT_VALUE_FNS_STRING = new String[] { "toString", "valueOf" };
|
||||
|
||||
static Object getDefaultValue(final JSObject jsobj, final Class<?> hint) throws UnsupportedOperationException {
|
||||
final boolean isNumber = hint == null || hint == Number.class;
|
||||
for(final String methodName: isNumber ? DEFAULT_VALUE_FNS_NUMBER : DEFAULT_VALUE_FNS_STRING) {
|
||||
final Object objMember = jsobj.getMember(methodName);
|
||||
if (objMember instanceof JSObject) {
|
||||
final JSObject member = (JSObject)objMember;
|
||||
if (member.isFunction()) {
|
||||
final Object value = member.call(jsobj);
|
||||
if (JSType.isPrimitive(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException(isNumber ? "cannot.get.default.number" : "cannot.get.default.string");
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ package jdk.nashorn.api.scripting;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
|
||||
/**
|
||||
* This interface can be implemented by an arbitrary Java class. Nashorn will
|
||||
@ -186,6 +187,22 @@ public interface JSObject {
|
||||
* Returns this object's numeric value.
|
||||
*
|
||||
* @return this object's numeric value.
|
||||
* @deprecated use {@link #getDefaultValue(Class)} with {@link Number} hint instead.
|
||||
*/
|
||||
public double toNumber();
|
||||
@Deprecated
|
||||
default double toNumber() {
|
||||
return JSType.toNumber(JSType.toPrimitive(this, Number.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements this object's {@code [[DefaultValue]]} method as per ECMAScript 5.1 section 8.6.2.
|
||||
*
|
||||
* @param hint the type hint. Should be either {@code null}, {@code Number.class} or {@code String.class}.
|
||||
* @return this object's default value.
|
||||
* @throws UnsupportedOperationException if the conversion can't be performed. The engine will convert this
|
||||
* exception into a JavaScript {@code TypeError}.
|
||||
*/
|
||||
default Object getDefaultValue(final Class<?> hint) throws UnsupportedOperationException {
|
||||
return DefaultValueImpl.getDefaultValue(this, hint);
|
||||
}
|
||||
}
|
||||
|
18
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/ScriptObjectMirror.java
18
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/ScriptObjectMirror.java
@ -46,6 +46,7 @@ import javax.script.Bindings;
|
||||
import jdk.nashorn.internal.objects.Global;
|
||||
import jdk.nashorn.internal.runtime.ConsString;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.ECMAException;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.ScriptFunction;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
@ -820,4 +821,21 @@ public final class ScriptObjectMirror extends AbstractJSObject implements Bindin
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDefaultValue(Class<?> hint) {
|
||||
return inGlobal(new Callable<Object>() {
|
||||
@Override public Object call() {
|
||||
try {
|
||||
return sobj.getDefaultValue(hint);
|
||||
} catch (final ECMAException e) {
|
||||
// We're catching ECMAException (likely TypeError), and translating it to
|
||||
// UnsupportedOperationException. This in turn will be translated into TypeError of the
|
||||
// caller's Global by JSType#toPrimitive(JSObject,Class) therefore ensuring that it's
|
||||
// recognized as "instanceof TypeError" in the caller.
|
||||
throw new UnsupportedOperationException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ import jdk.nashorn.internal.ir.FunctionNode;
|
||||
*/
|
||||
final class AstDeserializer {
|
||||
static FunctionNode deserialize(final byte[] serializedAst) {
|
||||
// FIXME: do we need this doPrivileged block at all?
|
||||
return AccessController.doPrivileged(new PrivilegedAction<FunctionNode>() {
|
||||
@Override
|
||||
public FunctionNode run() {
|
||||
|
@ -29,6 +29,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
|
||||
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;
|
||||
@ -210,7 +211,6 @@ public enum JSType {
|
||||
/** Method handle for void returns. */
|
||||
public static final Call VOID_RETURN = staticCall(JSTYPE_LOOKUP, JSType.class, "voidReturn", void.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
|
||||
@ -480,19 +480,49 @@ public enum JSType {
|
||||
* @return the primitive form of the object
|
||||
*/
|
||||
public static Object toPrimitive(final Object obj, final Class<?> hint) {
|
||||
return obj instanceof ScriptObject ? toPrimitive((ScriptObject)obj, hint) : obj;
|
||||
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) {
|
||||
final Object result = sobj.getDefaultValue(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 JSObject#getDefaultValue(Class)} and translates any thrown {@link UnsupportedOperationException}
|
||||
* to a 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(jsobj.getDefaultValue(hint));
|
||||
} catch (final UnsupportedOperationException e) {
|
||||
throw new ECMAException(Context.getGlobal().newTypeError(e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines a hintless toPrimitive and a toString call.
|
||||
*
|
||||
@ -724,6 +754,18 @@ public enum JSType {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -1301,6 +1343,10 @@ public enum JSType {
|
||||
return (String)obj;
|
||||
}
|
||||
|
||||
if (obj instanceof ConsString) {
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
if (obj instanceof Number) {
|
||||
return toString(((Number)obj).doubleValue());
|
||||
}
|
||||
@ -1313,23 +1359,19 @@ public enum JSType {
|
||||
return "null";
|
||||
}
|
||||
|
||||
if (obj instanceof ScriptObject) {
|
||||
if (safe) {
|
||||
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));
|
||||
if (obj instanceof Boolean) {
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
if (obj instanceof StaticClass) {
|
||||
return "[JavaClass " + ((StaticClass)obj).getRepresentedClass().getName() + "]";
|
||||
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 obj.toString();
|
||||
return toString(toPrimitive(obj, String.class));
|
||||
}
|
||||
|
||||
// trim from left for JS whitespaces.
|
||||
@ -1822,18 +1864,18 @@ public enum JSType {
|
||||
}
|
||||
|
||||
if (obj instanceof Boolean) {
|
||||
return (Boolean)obj ? 1 : +0.0;
|
||||
return toNumber((Boolean)obj);
|
||||
}
|
||||
|
||||
if (obj instanceof ScriptObject) {
|
||||
return toNumber((ScriptObject)obj);
|
||||
}
|
||||
|
||||
if (obj instanceof JSObject) {
|
||||
return ((JSObject)obj).toNumber();
|
||||
if (obj instanceof Undefined) {
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
return Double.NaN;
|
||||
return toNumber(toPrimitive(obj, Number.class));
|
||||
}
|
||||
|
||||
private static Object invoke(final MethodHandle mh, final Object arg) {
|
||||
|
@ -32,6 +32,7 @@ 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 java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.SwitchPoint;
|
||||
@ -720,7 +721,7 @@ public final class ScriptRuntime {
|
||||
return true;
|
||||
}
|
||||
if (x instanceof ScriptObject && y instanceof ScriptObject) {
|
||||
return x == y;
|
||||
return false; // x != y
|
||||
}
|
||||
if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) {
|
||||
return ScriptObjectMirror.identical(x, y);
|
||||
@ -784,37 +785,55 @@ public final class ScriptRuntime {
|
||||
* @return true if they're equal
|
||||
*/
|
||||
private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) {
|
||||
if (xType == JSType.UNDEFINED && yType == JSType.NULL || xType == JSType.NULL && yType == JSType.UNDEFINED) {
|
||||
if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (xType == JSType.NUMBER && yType == JSType.STRING) {
|
||||
return equals(x, JSType.toNumber(y));
|
||||
}
|
||||
|
||||
if (xType == JSType.STRING && yType == JSType.NUMBER) {
|
||||
return equals(JSType.toNumber(x), y);
|
||||
}
|
||||
|
||||
if (xType == JSType.BOOLEAN) {
|
||||
return equals(JSType.toNumber(x), y);
|
||||
}
|
||||
|
||||
if (yType == JSType.BOOLEAN) {
|
||||
return equals(x, JSType.toNumber(y));
|
||||
}
|
||||
|
||||
if ((xType == JSType.STRING || xType == JSType.NUMBER) && y instanceof ScriptObject) {
|
||||
return equals(x, JSType.toPrimitive(y));
|
||||
}
|
||||
|
||||
if (x instanceof ScriptObject && (yType == JSType.STRING || yType == JSType.NUMBER)) {
|
||||
return equals(JSType.toPrimitive(x), y);
|
||||
} 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 (isNumberOrStringAndObject(xType, yType)) {
|
||||
return equalNumberOrStringToObject(x, y);
|
||||
} else if (isNumberOrStringAndObject(yType, xType)) {
|
||||
// Can reverse order as y is primitive
|
||||
return equalNumberOrStringToObject(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 isNumberOrStringAndObject(final JSType xType, final JSType yType) {
|
||||
return (xType == JSType.NUMBER || xType == JSType.STRING) && 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 equalNumberOrStringToObject(final Object numOrStr, final Object any) {
|
||||
return equals(numOrStr, JSType.toPrimitive(any));
|
||||
}
|
||||
|
||||
/**
|
||||
* ECMA 11.9.4 - The strict equal operator (===) - generic implementation
|
||||
*
|
||||
|
@ -27,14 +27,10 @@ package jdk.nashorn.internal.runtime.linker;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.script.Bindings;
|
||||
import jdk.internal.dynalink.CallSiteDescriptor;
|
||||
import jdk.internal.dynalink.linker.GuardedInvocation;
|
||||
import jdk.internal.dynalink.linker.GuardedTypeConversion;
|
||||
import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
|
||||
import jdk.internal.dynalink.linker.LinkRequest;
|
||||
import jdk.internal.dynalink.linker.LinkerServices;
|
||||
import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
|
||||
@ -49,7 +45,7 @@ import jdk.nashorn.internal.runtime.JSType;
|
||||
* A Dynalink linker to handle web browser built-in JS (DOM etc.) objects as well
|
||||
* as ScriptObjects from other Nashorn contexts.
|
||||
*/
|
||||
final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTypeConverterFactory {
|
||||
final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
|
||||
private final NashornBeansLinker nashornBeansLinker;
|
||||
|
||||
JSObjectLinker(final NashornBeansLinker nashornBeansLinker) {
|
||||
@ -95,22 +91,6 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy
|
||||
return Bootstrap.asTypeSafeReturn(inv, linkerServices, desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
|
||||
final boolean sourceIsAlwaysJSObject = JSObject.class.isAssignableFrom(sourceType);
|
||||
if(!sourceIsAlwaysJSObject && !sourceType.isAssignableFrom(JSObject.class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final MethodHandle converter = CONVERTERS.get(targetType);
|
||||
if(converter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GuardedTypeConversion(new GuardedInvocation(converter, sourceIsAlwaysJSObject ? null : IS_JSOBJECT_GUARD).asType(MethodType.methodType(targetType, sourceType)), true);
|
||||
}
|
||||
|
||||
|
||||
private GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request, final LinkerServices linkerServices) throws Exception {
|
||||
final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
|
||||
final int c = desc.getNameTokenCount();
|
||||
@ -208,25 +188,6 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static int toInt32(final JSObject obj) {
|
||||
return JSType.toInt32(toNumber(obj));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static long toLong(final JSObject obj) {
|
||||
return JSType.toLong(toNumber(obj));
|
||||
}
|
||||
|
||||
private static double toNumber(final JSObject obj) {
|
||||
return obj == null ? 0 : obj.toNumber();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean toBoolean(final JSObject obj) {
|
||||
return obj != null;
|
||||
}
|
||||
|
||||
private static int getIndex(final Number n) {
|
||||
final double value = n.doubleValue();
|
||||
return JSType.isRepresentableAsInt(value) ? (int)value : -1;
|
||||
@ -261,14 +222,6 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTy
|
||||
private static final MethodHandle JSOBJECT_CALL_TO_APPLY = findOwnMH_S("callToApply", Object.class, MethodHandle.class, JSObject.class, Object.class, Object[].class);
|
||||
private static final MethodHandle JSOBJECT_NEW = findJSObjectMH_V("newObject", Object.class, Object[].class);
|
||||
|
||||
private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
|
||||
static {
|
||||
CONVERTERS.put(boolean.class, findOwnMH_S("toBoolean", boolean.class, JSObject.class));
|
||||
CONVERTERS.put(int.class, findOwnMH_S("toInt32", int.class, JSObject.class));
|
||||
CONVERTERS.put(long.class, findOwnMH_S("toLong", long.class, JSObject.class));
|
||||
CONVERTERS.put(double.class, findOwnMH_S("toNumber", double.class, JSObject.class));
|
||||
}
|
||||
|
||||
private static MethodHandle findJSObjectMH_V(final String name, final Class<?> rtype, final Class<?>... types) {
|
||||
return MH.findVirtual(MethodHandles.lookup(), JSObject.class, name, MH.type(rtype, types));
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ reduceRight 12 3
|
||||
reduceRight 15 1
|
||||
right sum 16
|
||||
squared 1,9,25,49
|
||||
iterating on [object Array]
|
||||
iterating on 2,4,6,8
|
||||
forEach 2
|
||||
forEach 4
|
||||
forEach 6
|
||||
|
@ -102,7 +102,18 @@ print(jlist instanceof java.util.List);
|
||||
print(jlist);
|
||||
|
||||
var obj = new JSObject() {
|
||||
toNumber: function() { return 42; }
|
||||
getMember: function(name) {
|
||||
if (name == "valueOf") {
|
||||
return new JSObject() {
|
||||
isFunction: function() {
|
||||
return true;
|
||||
},
|
||||
call: function(thiz) {
|
||||
return 42;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
print(32 + obj);
|
||||
|
164
nashorn/test/script/basic/JDK-8072426.js
Normal file
164
nashorn/test/script/basic/JDK-8072426.js
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* JDK-8072426: Can't compare Java objects to strings or numbers
|
||||
*
|
||||
* @test
|
||||
* @run
|
||||
*/
|
||||
|
||||
Assert.assertTrue(java.math.RoundingMode.UP == "UP");
|
||||
|
||||
var JSObject = Java.type("jdk.nashorn.api.scripting.JSObject");
|
||||
|
||||
// Adds an "isFunction" member to the JSObject that returns the specified value
|
||||
function addIsFunction(isFunction, obj) {
|
||||
obj.isFunction = function() {
|
||||
return isFunction;
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
function makeJSObjectConstantFunction(value) {
|
||||
return new JSObject(addIsFunction(true, {
|
||||
call: function() {
|
||||
return value;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function makeJSObjectWithMembers(mapping) {
|
||||
return new JSObject({
|
||||
getMember: function(name) {
|
||||
Assert.assertTrue(mapping.hasOwnProperty(name));
|
||||
return mapping[name];
|
||||
},
|
||||
toNumber: function() {
|
||||
// toNumber no longer invoked
|
||||
Assert.fail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test JSObjectLinker toInt32/toLong/toNumber
|
||||
function testNumericJSObject(kind, value) {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
valueOf: makeJSObjectConstantFunction(value)
|
||||
});
|
||||
|
||||
if (kind === "double") {
|
||||
// There's no assertEquals(double actual, double expected). There's only
|
||||
// assertEquals(double actual, double expected, double delta).
|
||||
Assert["assertEquals(double,double,double)"](value, obj, 0);
|
||||
} else {
|
||||
Assert["assertEquals(" + kind + ", " + kind + ")"](value, obj);
|
||||
}
|
||||
Assert.assertTrue(value == Number(obj));
|
||||
}
|
||||
testNumericJSObject("int", 42);
|
||||
testNumericJSObject("long", 4294967296);
|
||||
testNumericJSObject("double", 1.2);
|
||||
|
||||
// Test fallback from toNumber to toString for numeric conversion when toNumber doesn't exist
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
valueOf: null, // Explicitly no valueOf
|
||||
toString: makeJSObjectConstantFunction("123")
|
||||
});
|
||||
Assert["assertEquals(int,int)"](123, obj);
|
||||
})();
|
||||
|
||||
// Test fallback from toNumber to toString for numeric conversion when toNumber isn't a callable
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
valueOf: new JSObject(addIsFunction(false, {})),
|
||||
toString: makeJSObjectConstantFunction("124")
|
||||
});
|
||||
Assert["assertEquals(int,int)"](124, obj);
|
||||
})();
|
||||
|
||||
// Test fallback from toNumber to toString for numeric conversion when toNumber returns a non-primitive
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
valueOf: makeJSObjectConstantFunction({}),
|
||||
toString: makeJSObjectConstantFunction("125")
|
||||
});
|
||||
Assert["assertEquals(int,int)"](125, obj);
|
||||
})();
|
||||
|
||||
// Test TypeError from toNumber to toString when both return a non-primitive
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
valueOf: makeJSObjectConstantFunction({}),
|
||||
toString: makeJSObjectConstantFunction({})
|
||||
});
|
||||
try {
|
||||
Number(obj);
|
||||
Assert.fail(); // must throw
|
||||
} catch(e) {
|
||||
Assert.assertTrue(e instanceof TypeError);
|
||||
}
|
||||
})();
|
||||
|
||||
// Test toString for string conversion
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
toString: makeJSObjectConstantFunction("Hello")
|
||||
});
|
||||
Assert.assertTrue("Hello" === String(obj));
|
||||
Assert["assertEquals(String,String)"]("Hello", obj);
|
||||
})();
|
||||
|
||||
// Test fallback from toString to valueOf for string conversion when toString doesn't exist
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
toString: null,
|
||||
valueOf: makeJSObjectConstantFunction("Hello1")
|
||||
});
|
||||
Assert.assertTrue("Hello1" === String(obj));
|
||||
Assert["assertEquals(String,String)"]("Hello1", obj);
|
||||
})();
|
||||
|
||||
// Test fallback from toString to valueOf for string conversion when toString is not callable
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
toString: new JSObject(addIsFunction(false, {})),
|
||||
valueOf: makeJSObjectConstantFunction("Hello2")
|
||||
});
|
||||
Assert["assertEquals(String,String)"]("Hello2", obj);
|
||||
})();
|
||||
|
||||
// Test fallback from toString to valueOf for string conversion when toString returns non-primitive
|
||||
(function() {
|
||||
var obj = makeJSObjectWithMembers({
|
||||
toString: makeJSObjectConstantFunction({}),
|
||||
valueOf: makeJSObjectConstantFunction("Hello3")
|
||||
});
|
||||
Assert["assertEquals(String,String)"]("Hello3", obj);
|
||||
})();
|
||||
|
||||
// Test toBoolean for JSObject
|
||||
(function() {
|
||||
Assert["assertEquals(boolean,boolean)"](true, new JSObject({}));
|
||||
})();
|
Loading…
x
Reference in New Issue
Block a user