JDK-8053910 : ScriptObjectMirror causing havoc with Invocation interface
  • Type: Bug
  • Component: core-libs
  • Sub-Component: jdk.nashorn
  • Affected Version: 8u40
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2014-07-29
  • Updated: 2015-01-21
  • Resolved: 2014-08-06
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u40Fixed 9 b28Fixed
Related Reports
Relates :  
Description
http://mail.openjdk.java.net/pipermail/nashorn-dev/2014-July/003204.html

Inconsistency about conversion to ScriptObjectMirror with getInterface and invokeFunction makes it difficult know which kind of object is being worked with in Nashorn.  Example;

package mirrors;

import java.util.*;
import javax.script.*;

public class Mirrors {
    public interface Example {
        Object test1(Object arg);
        Object test2(Object arg);
    }
    
    public static void main(String[] args) throws Exception {
        ScriptEngineManager engineManager = new ScriptEngineManager();
        ScriptEngine engine = engineManager.getEngineByName("nashorn");
        Invocable invocable = (Invocable)engine;

        engine.eval("function test1(arg) { print(arg.class); return { arg: arg }; }");
        engine.eval("function test2(arg) { print(arg.class); return arg; }");

        Map<String, Object> map = new HashMap<>();
        map.put("option", true);

        Example example = invocable.getInterface(Example.class);
        
        Object value1 = invocable.invokeFunction("test1", map);
        Object value2 = example.test1(map);
        Object value3 = invocable.invokeFunction("test2", value2);
        Object value4 = example.test2(value2);

        System.out.println(value1.getClass());
        System.out.println(value2.getClass());
        System.out.println(value3.getClass());
        System.out.println(value4.getClass());
    }
}


Output:

class java.util.HashMap
class java.util.HashMap
undefined
undefined
class jdk.nashorn.api.scripting.ScriptObjectMirror
class jdk.nashorn.internal.scripts.JO4
class jdk.nashorn.api.scripting.ScriptObjectMirror
class jdk.nashorn.internal.scripts.JO4

Comments
I have two sides to this story. 1) The definition of getInterface describes "The methods of the interface may be implemented using the invokeFunction method.", so the expectation is that the results should have been the same for both. 2) Investigation of the "Nashorn and AsciidoctorJs" email shows that the behaviour of ScriptObjectMirror and ScriptObject can be very different and confusing from the JavaScript perspective. In that case, invokeFunction unwrapped the ScriptObjectMirror, which was good and getInterface did not, which is consistent but caused problems down the line.
30-07-2014

Do we want to accept this as "bug"? If so, design decision has to be changed - namely, we should wrap ScriptObjects as ScriptObjectMirrors whereever Java method requires / return Object type. Are we okay with that? I can't think of any other way to fix it (if we consider this as bug).
30-07-2014

In a way, it is a known design decision: 1) All API entry points in javax.script package that return result from a script execution - ScriptEngine.eval, Invocable.invokeMethod, Invocable.invokeFunction - wrap script object (ScriptObject instances) as ScriptObjectMirror. This is because we expect that the users may operate by calling script object's functionality on the resulting object. 2) For any other Java method input or return result from Java interface/class implemented by script with a static type of Object, we pass ScriptObject "as is". No wrapping. The idea is that general Java method that accepts Object should be ready to accept any Object including ScriptObject and also when a java method returns Object type, any Object can be returned. Now, if we want interfaces returned by Invocable interface alone to be different, then we would have inconsistency w.r.t interface objects created within script itself (and user could pass that to some other object and see that difference). The crux is that we can not convert ScriptObject to ScriptObjectMirror for all paths that accept java.lang.Object (without making the whole Java access mechanism really expensive).
30-07-2014

Two problems: 1) Not wrapping ScriptObjects as ScriptObjectMirrors for Java method arguments that need "Object" or Java methods that return Object (when implementing interface or abstract class override in script). This step will avoid any ScriptObject escape to java land. 2) JavaAdapter should unwrap ScriptObjectMirrors that come from Java land. i.e., Java method arguments of type Object and ScriptObjectMirror/JSObject/Map/Bindings should be unwrapped to be ScriptObjects - this will make sure script function implementing interface method will always see ScriptObjects rather than ScriptObjectMirrors (even mirror is associated with current global).
30-07-2014