changeset 375:51a5ee93d6bc

8015959: Can't call foreign constructor Reviewed-by: jlaskey, hannesw
author sundar
date Mon, 24 Jun 2013 19:06:01 +0530
parents 2ded2fc08c94
children 26a345c26e62
files src/jdk/nashorn/api/scripting/JSObject.java src/jdk/nashorn/api/scripting/ScriptObjectMirror.java src/jdk/nashorn/internal/runtime/ScriptFunction.java src/jdk/nashorn/internal/runtime/ScriptFunctionData.java src/jdk/nashorn/internal/runtime/ScriptRuntime.java src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java test/script/basic/JDK-8015959.js test/script/basic/JDK-8015959.js.EXPECTED
diffstat 8 files changed, 245 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk/nashorn/api/scripting/JSObject.java	Sat Jun 22 10:12:19 2013 -0300
+++ b/src/jdk/nashorn/api/scripting/JSObject.java	Mon Jun 24 19:06:01 2013 +0530
@@ -30,13 +30,23 @@
  */
 public abstract class JSObject {
     /**
-     * Call a JavaScript method
+     * Call a JavaScript function
      *
-     * @param methodName name of method
+     * @param functionName name of function
      * @param args arguments to method
      * @return result of call
      */
-    public abstract Object call(String methodName, Object args[]);
+    public abstract Object call(String functionName, Object... args);
+
+    /**
+     * Call a JavaScript method as a constructor. This is equivalent to
+     * calling new obj.Method(arg1, arg2...) in JavaScript.
+     *
+     * @param functionName name of function
+     * @param args arguments to method
+     * @return result of constructor call
+     */
+    public abstract Object newObject(String functionName, Object... args);
 
     /**
      * Evaluate a JavaScript expression
--- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Sat Jun 22 10:12:19 2013 -0300
+++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Mon Jun 24 19:06:01 2013 +0530
@@ -102,7 +102,7 @@
 
     // JSObject methods
     @Override
-    public Object call(final String methodName, final Object args[]) {
+    public Object call(final String functionName, final Object... args) {
         final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal();
         final boolean globalChanged = (oldGlobal != global);
 
@@ -111,9 +111,9 @@
                 NashornScriptEngine.setNashornGlobal(global);
             }
 
-            final Object val = sobj.get(methodName);
+            final Object val = functionName == null? sobj : sobj.get(functionName);
             if (! (val instanceof ScriptFunction)) {
-                throw new RuntimeException("No such method: " + methodName);
+                throw new RuntimeException("No such function " + ((functionName != null)? functionName : ""));
             }
 
             final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
@@ -130,6 +130,34 @@
     }
 
     @Override
+    public Object newObject(final String functionName, final Object... args) {
+        final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal();
+        final boolean globalChanged = (oldGlobal != global);
+
+        try {
+            if (globalChanged) {
+                NashornScriptEngine.setNashornGlobal(global);
+            }
+
+            final Object val = functionName == null? sobj : sobj.get(functionName);
+            if (! (val instanceof ScriptFunction)) {
+                throw new RuntimeException("not a constructor " + ((functionName != null)? functionName : ""));
+            }
+
+            final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
+            return wrap(ScriptRuntime.checkAndConstruct((ScriptFunction)val, unwrapArray(modArgs, global)), global);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        } finally {
+            if (globalChanged) {
+                NashornScriptEngine.setNashornGlobal(oldGlobal);
+            }
+        }
+    }
+
+    @Override
     public Object eval(final String s) {
         return inGlobal(new Callable<Object>() {
             @Override
--- a/src/jdk/nashorn/internal/runtime/ScriptFunction.java	Sat Jun 22 10:12:19 2013 -0300
+++ b/src/jdk/nashorn/internal/runtime/ScriptFunction.java	Mon Jun 24 19:06:01 2013 +0530
@@ -203,6 +203,16 @@
     }
 
     /**
+     * Execute this script function as a constructor.
+     * @param arguments  Call arguments.
+     * @return Newly constructed result.
+     * @throws Throwable if there is an exception/error with the invocation or thrown from it
+     */
+    Object construct(final Object... arguments) throws Throwable {
+        return data.construct(this, arguments);
+    }
+
+    /**
      * Allocate function. Called from generated {@link ScriptObject} code
      * for allocation as a factory method
      *
--- a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java	Sat Jun 22 10:12:19 2013 -0300
+++ b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java	Mon Jun 24 19:06:01 2013 +0530
@@ -216,6 +216,12 @@
         return composeGenericMethod(code.mostGeneric().getInvoker());
     }
 
+    final MethodHandle getGenericConstructor() {
+        ensureCodeGenerated();
+        ensureConstructor(code.mostGeneric());
+        return composeGenericMethod(code.mostGeneric().getConstructor());
+    }
+
     private CompiledFunction getBest(final MethodType callSiteType) {
         ensureCodeGenerated();
         return code.best(callSiteType);
@@ -535,10 +541,74 @@
         }
     }
 
+    Object construct(final ScriptFunction fn, final Object... arguments) throws Throwable {
+        final MethodHandle mh = getGenericConstructor();
+
+        final Object[]     args       = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;
+
+        if (isVarArg(mh)) {
+            if (needsCallee(mh)) {
+                return mh.invokeExact(fn, args);
+            }
+            return mh.invokeExact(args);
+        }
+
+        final int paramCount = mh.type().parameterCount();
+        if (needsCallee(mh)) {
+            switch (paramCount) {
+            case 1:
+                return mh.invokeExact(fn);
+            case 2:
+                return mh.invokeExact(fn, getArg(args, 0));
+            case 3:
+                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1));
+            case 4:
+                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1), getArg(args, 2));
+            default:
+                return mh.invokeWithArguments(withArguments(fn, paramCount, args));
+            }
+        }
+
+        switch (paramCount) {
+        case 0:
+            return mh.invokeExact();
+        case 1:
+            return mh.invokeExact(getArg(args, 0));
+        case 2:
+            return mh.invokeExact(getArg(args, 0), getArg(args, 1));
+        case 3:
+            return mh.invokeExact(getArg(args, 0), getArg(args, 1), getArg(args, 2));
+        default:
+            return mh.invokeWithArguments(withArguments(null, paramCount, args));
+        }
+    }
+
     private static Object getArg(final Object[] args, final int i) {
         return i < args.length ? args[i] : UNDEFINED;
     }
 
+    private static Object[] withArguments(final ScriptFunction fn, final int argCount, final Object[] args) {
+        final Object[] finalArgs = new Object[argCount];
+
+        int nextArg = 0;
+        if (fn != null) {
+            //needs callee
+            finalArgs[nextArg++] = fn;
+        }
+
+        // Don't add more args that there is argCount in the handle (including self and callee).
+        for (int i = 0; i < args.length && nextArg < argCount;) {
+            finalArgs[nextArg++] = args[i++];
+        }
+
+        // If we have fewer args than argCount, pad with undefined.
+        while (nextArg < argCount) {
+            finalArgs[nextArg++] = UNDEFINED;
+        }
+
+        return finalArgs;
+    }
+
     private static Object[] withArguments(final ScriptFunction fn, final Object self, final int argCount, final Object[] args) {
         final Object[] finalArgs = new Object[argCount];
 
--- a/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Sat Jun 22 10:12:19 2013 -0300
+++ b/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Mon Jun 24 19:06:01 2013 +0530
@@ -361,6 +361,47 @@
     }
 
     /**
+     * Check that the target function is associated with current Context.
+     * And also make sure that 'self', if ScriptObject, is from current context.
+     *
+     * Call a function as a constructor given args.
+     *
+     * @param target ScriptFunction object.
+     * @param args   Call arguments.
+     * @return Constructor call result.
+     */
+    public static Object checkAndConstruct(final ScriptFunction target, final Object... args) {
+        final ScriptObject global = Context.getGlobalTrusted();
+        if (! (global instanceof GlobalObject)) {
+            throw new IllegalStateException("No current global set");
+        }
+
+        if (target.getContext() != global.getContext()) {
+            throw new IllegalArgumentException("'target' function is not from current Context");
+        }
+
+        // all in order - call real 'construct'
+        return construct(target, args);
+    }
+
+    /*
+     * Call a script function as a constructor with given args.
+     *
+     * @param target ScriptFunction object.
+     * @param args   Call arguments.
+     * @return Constructor call result.
+     */
+    public static Object construct(final ScriptFunction target, final Object... args) {
+        try {
+            return target.construct(args);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
+    /**
      * Generic implementation of ECMA 9.12 - SameValue algorithm
      *
      * @param x first value to compare
--- a/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Sat Jun 22 10:12:19 2013 -0300
+++ b/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Mon Jun 24 19:06:01 2013 +0530
@@ -45,44 +45,14 @@
  * as ScriptObjects from other Nashorn contexts.
  */
 final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
-   /**
-     * Instances of this class are used to represent a method member of a JSObject
-     */
-    private static final class JSObjectMethod {
-        // The name of the JSObject method property
-        private final String name;
-
-        JSObjectMethod(final String name) {
-            this.name = name;
-        }
-
-        String getName() {
-            return name;
-        }
-
-        static GuardedInvocation lookup(final CallSiteDescriptor desc) {
-            final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
-            switch (operator) {
-                case "call": {
-                    // collect everything except the first two - JSObjectMethod instance and the actual 'self'
-                    final int paramCount = desc.getMethodType().parameterCount();
-                    final MethodHandle caller = MH.asCollector(JSOBJECTMETHOD_CALL, Object[].class, paramCount - 2);
-                    return new GuardedInvocation(caller, null, IS_JSOBJECTMETHOD_GUARD);
-                }
-                default:
-                    return null;
-            }
-        }
-    }
-
     @Override
     public boolean canLinkType(final Class<?> type) {
         return canLinkTypeStatic(type);
     }
 
     static boolean canLinkTypeStatic(final Class<?> type) {
-        // can link JSObject and JSObjectMethod
-        return JSObject.class.isAssignableFrom(type) || JSObjectMethod.class.isAssignableFrom(type);
+        // can link JSObject
+        return JSObject.class.isAssignableFrom(type);
     }
 
     @Override
@@ -99,8 +69,6 @@
         final GuardedInvocation inv;
         if (self instanceof JSObject) {
             inv = lookup(desc);
-        } else if (self instanceof JSObjectMethod) {
-            inv = JSObjectMethod.lookup(desc);
         } else {
             throw new AssertionError(); // Should never reach here.
         }
@@ -115,7 +83,7 @@
             case "getProp":
             case "getElem":
             case "getMethod":
-                return c > 2 ? findGetMethod(desc, operator) : findGetIndexMethod();
+                return c > 2 ? findGetMethod(desc) : findGetIndexMethod();
             case "setProp":
             case "setElem":
                 return c > 2 ? findSetMethod(desc) : findSetIndexMethod();
@@ -123,15 +91,14 @@
             case "callMethod":
                 return findCallMethod(desc, operator);
             case "new":
+                return findNewMethod(desc);
             default:
                 return null;
         }
     }
 
-    private static GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final String operator) {
-        // if "getMethod" then return JSObjectMethod object - which just holds the name of the method
-        // subsequently, link on dyn:call for JSObjectMethod will actually call that method
-        final MethodHandle getter = MH.insertArguments("getMethod".equals(operator)? JSOBJECT_GETMETHOD : JSOBJECT_GET, 1, desc.getNameToken(2));
+    private static GuardedInvocation findGetMethod(final CallSiteDescriptor desc) {
+        final MethodHandle getter = MH.insertArguments(JSOBJECT_GET, 1, desc.getNameToken(2));
         return new GuardedInvocation(getter, null, IS_JSOBJECT_GUARD);
     }
 
@@ -156,9 +123,9 @@
         return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
     }
 
-    @SuppressWarnings("unused")
-    private static boolean isJSObjectMethod(final Object self) {
-        return self instanceof JSObjectMethod;
+    private static GuardedInvocation findNewMethod(final CallSiteDescriptor desc) {
+        MethodHandle func = MH.asCollector(JSOBJECT_NEW, Object[].class, desc.getMethodType().parameterCount() - 1);
+        return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
     }
 
     @SuppressWarnings("unused")
@@ -166,12 +133,6 @@
         return self instanceof JSObject;
     }
 
-
-    @SuppressWarnings("unused")
-    private static Object getMethod(final Object jsobj, final Object key) {
-        return new JSObjectMethod(Objects.toString(key));
-    }
-
     @SuppressWarnings("unused")
     private static Object get(final Object jsobj, final Object key) {
         if (key instanceof String) {
@@ -200,11 +161,8 @@
     }
 
     @SuppressWarnings("unused")
-    private static Object jsObjectMethodCall(final Object jsObjMethod, final Object jsobj, final Object... args) {
-        // we have JSObjectMethod, JSObject and args. Get method name from JSObjectMethod instance
-        final String methodName = ((JSObjectMethod)jsObjMethod).getName();
-        // call the method on JSObject
-        return ((JSObject)jsobj).call(methodName, args);
+    private static Object newObject(final Object jsobj, final Object... args) {
+        return ((JSObject)jsobj).newObject(null, args);
     }
 
     private static int getIndex(final Number n) {
@@ -214,13 +172,11 @@
 
     private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
 
-    private static final MethodHandle IS_JSOBJECTMETHOD_GUARD = findOwnMH("isJSObjectMethod", boolean.class, Object.class);
     private static final MethodHandle IS_JSOBJECT_GUARD = findOwnMH("isJSObject", boolean.class, Object.class);
-    private static final MethodHandle JSOBJECT_GETMETHOD = findOwnMH("getMethod", Object.class, Object.class, Object.class);
     private static final MethodHandle JSOBJECT_GET = findOwnMH("get", Object.class, Object.class, Object.class);
     private static final MethodHandle JSOBJECT_PUT = findOwnMH("put", Void.TYPE, Object.class, Object.class, Object.class);
     private static final MethodHandle JSOBJECT_CALL = findOwnMH("call", Object.class, Object.class, Object.class, Object[].class);
-    private static final MethodHandle JSOBJECTMETHOD_CALL = findOwnMH("jsObjectMethodCall", Object.class, Object.class, Object.class, Object[].class);
+    private static final MethodHandle JSOBJECT_NEW = findOwnMH("newObject", Object.class, Object.class, Object[].class);
 
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
         final Class<?>   own = JSObjectLinker.class;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8015959.js	Mon Jun 24 19:06:01 2013 +0530
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * 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-8015959: Can't call foreign constructor
+ *
+ * @test
+ * @run
+ */
+
+function check(global) {
+    var obj = new global.Point(344, 12);
+    print("obj.x " + obj.x);
+    print("obj.y " + obj.y);
+    print("obj instanceof global.Point? " + (obj instanceof global.Point))
+
+    var P = global.Point;
+    var p = new P(343, 54);
+    print("p.x " + p.x);
+    print("p.y " + p.y);
+    print("p instanceof P? " + (p instanceof P))
+}
+
+print("check with loadWithNewGlobal");
+check(loadWithNewGlobal({
+   name: "myscript",
+   script: "function Point(x, y) { this.x = x; this.y = y }; this"
+}));
+
+print("check with script engine");
+var m = new javax.script.ScriptEngineManager();
+var e = m.getEngineByName('nashorn');
+check(e.eval("function Point(x, y) { this.x = x; this.y = y }; this"));
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8015959.js.EXPECTED	Mon Jun 24 19:06:01 2013 +0530
@@ -0,0 +1,14 @@
+check with loadWithNewGlobal
+obj.x 344
+obj.y 12
+obj instanceof global.Point? true
+p.x 343
+p.y 54
+p instanceof P? true
+check with script engine
+obj.x 344
+obj.y 12
+obj instanceof global.Point? true
+p.x 343
+p.y 54
+p instanceof P? true