changeset 29133:1cd7d8af99ba

8072426: Can't compare Java objects to strings or numbers Reviewed-by: hannesw, lagergren, sundar
author attila
date Fri, 20 Feb 2015 15:47:28 +0100
parents d6224d602145
children 7242c750aa76
files nashorn/make/nbproject/project.xml nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/DefaultValueImpl.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/JSObject.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/api/scripting/ScriptObjectMirror.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AstDeserializer.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/JSType.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java nashorn/test/script/basic/JDK-8023026.js.EXPECTED nashorn/test/script/basic/JDK-8024847.js nashorn/test/script/basic/JDK-8072426.js
diffstat 11 files changed, 380 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- 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({}));
+})();