This commit is contained in:
Lana Steuck 2015-02-26 20:16:58 -08:00
commit f8e7c24ceb
12 changed files with 382 additions and 104 deletions

@ -44,11 +44,11 @@ ifdef MILESTONE
endif
endif
# Need to use source and target 7 for nasgen to work.
# Need to use source and target 8 for nasgen to work.
$(eval $(call SetupJavaCompiler,GENERATE_NEWBYTECODE_DEBUG, \
JVM := $(JAVA), \
JAVAC := $(NEW_JAVAC), \
FLAGS := -g -source 7 -target 7 -bootclasspath "$(JDK_CLASSES)", \
FLAGS := -g -source 8 -target 8 -bootclasspath "$(JDK_CLASSES)", \
SERVER_DIR := $(SJAVAC_SERVER_DIR), \
SERVER_JVM := $(SJAVAC_SERVER_JAVA)))

@ -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>

@ -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);
}
}

@ -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);

@ -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({}));
})();