8072426: Can't compare Java objects to strings or numbers
Reviewed-by: hannesw, lagergren, sundar
--- a/nashorn/make/nbproject/project.xml Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/make/nbproject/project.xml Fri Feb 20 15:47:28 2015 +0100
@@ -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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/DefaultValueImpl.java Fri Feb 20 15:47:28 2015 +0100
@@ -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");
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/JSObject.java Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/JSObject.java Fri Feb 20 15:47:28 2015 +0100
@@ -27,6 +27,7 @@
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 @@
* 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);
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/ScriptObjectMirror.java Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/ScriptObjectMirror.java Fri Feb 20 15:47:28 2015 +0100
@@ -46,6 +46,7 @@
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 @@
}
});
}
+
+ @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);
+ }
+ }
+ });
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AstDeserializer.java Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AstDeserializer.java Fri Feb 20 15:47:28 2015 +0100
@@ -38,7 +38,6 @@
*/
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() {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/JSType.java Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/JSType.java Fri Feb 20 15:47:28 2015 +0100
@@ -29,6 +29,7 @@
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 @@
/** 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,17 +480,47 @@
* @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;
+ }
- 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);
+ }
}
/**
@@ -725,6 +755,18 @@
/**
+ * 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 @@
return (String)obj;
}
+ if (obj instanceof ConsString) {
+ return obj.toString();
+ }
+
if (obj instanceof Number) {
return toString(((Number)obj).doubleValue());
}
@@ -1313,23 +1359,19 @@
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 @@
}
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) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java Fri Feb 20 15:47:28 2015 +0100
@@ -32,6 +32,7 @@
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 @@
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 @@
* @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
*
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java Fri Feb 20 15:47:28 2015 +0100
@@ -27,14 +27,10 @@
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 @@
* 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 @@
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 @@
}
}
- @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 @@
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));
}
--- a/nashorn/test/script/basic/JDK-8023026.js.EXPECTED Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/test/script/basic/JDK-8023026.js.EXPECTED Fri Feb 20 15:47:28 2015 +0100
@@ -26,7 +26,7 @@
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
--- a/nashorn/test/script/basic/JDK-8024847.js Wed Jul 05 20:21:13 2017 +0200
+++ b/nashorn/test/script/basic/JDK-8024847.js Fri Feb 20 15:47:28 2015 +0100
@@ -102,7 +102,18 @@
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);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8072426.js Fri Feb 20 15:47:28 2015 +0100
@@ -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({}));
+})();