3

Running on GraalVM CE.

openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment (build 11.0.5+10-jvmci-19.3-b05-LTS)
OpenJDK 64-Bit GraalVM CE 19.3.0 (build 11.0.5+10-jvmci-19.3-b05-LTS, mixed mode, sharing)

Case 1:

import org.graalvm.polyglot.Context;

public class Test {

    static class Data {
        public String name = "HelloWorld";
        public String getName() {
            return this.name;
        }
    }

    public static void main(String[] args) {
        Context context = Context.newBuilder("js").allowHostAccess(true).build();
        context.getBindings("js").putMember("d", new Data());

        context.eval("js", "var x = d.name");

        System.out.println(
                context.getBindings("js").getMember("x").asString()
        );
    }
}

Result:

null

Why?

As I can understand, d passed correctly:

((Data) context.getBindings("js").getMember("d").as(Data.class)).name

returns "HelloWorld".

Case 2:

context.eval("js", "d.getName()");

Exception

Exception in thread "main" TypeError: invokeMember (getName) 
on JavaObject[task.Test$Data@35a3d49f (task.Test$Data)] failed due to: 
Unknown identifier: getName

But getName is public... What's wrong?

4 Answers 4

4
+50

When you use a context and add a Java Object to it, behind the scene, the IntropLibrary inside TruffleApi creates a HostObject and associate it with that object. This means that you don't use the object itself but rather a wrapper object.

When you call the getMember() method, the IntropLibrary can only access fields and methods of the hosted object that are publicly available. Since your inner class has default access (no access modifier), the API cannot find its members even though they are public. (a member of a class cannot have broader access than its class itself).

To solve this issue, all you have to do is make you inner class public

import org.graalvm.polyglot.Context;

public class Test {

  public static class Data {
    public String name = "HelloWorld";
    public String getName() {
        return this.name;
    }
  }

  public static void main(String[] args) {
    Context context = Context.newBuilder("js").allowHostAccess(true).build();
    context.getBindings("js").putMember("d", new Data());

    context.eval("js", "var x = d.name;");

    System.out.println(
        context.getBindings("js").getMember("x").asString()
    );
  }
}
Sign up to request clarification or add additional context in comments.

Comments

2

You need to annotate class field and method with @HostAccess.Export

By default only public classes, methods, and fields that are annotated with @HostAccess.Export are accessible to the guest language. This policy can be customized using Context.Builder.allowHostAccess(HostAccess) when constructing the context.

Example using a Java object from JavaScript:

 public class JavaRecord {
     @HostAccess.Export public int x;    
     @HostAccess.Export
     public String name() {
         return "foo";
     }
 }

Alternatively, you can use GraalVM JSR-223 ScriptEngine

GraalVM JavaScript provides a JSR-223 compliant javax.script.ScriptEngine implementation. Note that this is feature is provided for legacy reasons in order to allow easier migration for implementations currently based on a ScriptEngine. We strongly encourage users to use the org.graalvm.polyglot.Context interface

To set an option via Bindings, use Bindings.put(, true) before the engine's script context is initialized. Note that even a call to Bindings#get(String) may lead to context initialization. The following code shows how to enable polyglot.js.allowHostAccess via Bindings:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
bindings.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // would not work 

without allowHostAccess and allowHostClassLookup This example would not work if the user would call e.g. engine.eval("var x = 1;") before calling bindings.put("polyglot.js.allowHostAccess", true);, since any call to eval forces context initialization.

4 Comments

At first I don't want use JSR-223, because as said in your citation "this is feature is provided for legacy reasons". And secondly I tried such way without success. I am looking for solution with polyglot Context.
@Woland updated, You need to annotate class field and method with @HostAccess.Export
Thanks, but I still get null in Case 1.
My bad, works with Export. Need some time to investigate some cases.
0

GraalVM JavaScript enforces strict sandboxing rules by default, one of them being that JavaScript code cannot access host Java objects unless it is explicitly allowed by the user. The simplest way to allow your code to access context.eval("js", "d.getName()") is to pass the option polyglot.js.allowAllAccess=true as described in below link:

GraalVM JavaScript ScriptEngine implementation

Have a look at the Sample:

import org.graalvm.polyglot.Context;

public class Test {

    static class Data {
        public String name = "HelloWorld";
        public String getName() {
            return this.name;
        }
    }

    public static void main(String[] args) {
        Context context = Context.newBuilder("js").allowHostAccess(true).build();
        context.getBindings("js").putMember("d", new Data());

        context.eval("js", "var x = d.getName()");

        System.out.println(
                context.getBindings("js").getMember("d").as(Data.class)).name
        );
    }
}

1 Comment

Notice that I already use allowHostAccess(true). I also tried settings from your link Value bindings = context.getBindings("js"); bindings.putMember("polyglot.js.allowHostAccess", true); bindings.putMember("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);. No success.
0

To get full access to a java object in the usual js way, you can use the sj4js library.

This example is taken from the documentation...

public class TestObject extends JsProxyObject {
    
    // the property of the object
    private String name = "";
    
    // the constructor with the property 
    public TestObject (String name) {
        super ();
        
        this.name = name;
        
        // we hvae to initialize the proxy object
        // with all properties of this object
        init(this);
    }

    // this is a mandatory override, 
    // the proxy object needs this method 
    // to generate new objects if necessary
    @Override
    protected Object newInstance() {
        return new TestClass(this.name);
    }
    
    // the setter for the property name
    public void setName (String s) {
        this.name = s;
    }
    
    // the getter for the property name
    public String getName () {
        return this.name;
    }
}

And you can access this object as you would access a java object.

try (JScriptEngine engine = new JScriptEngine()) {
    engine.addObject("test", new TestClass("123"));
            
    engine.exec("test.name");
    // returns "123"

    engine.exec("test['name']")
    // returns "123"

    engine.exec("test.name = '456'")   
    engine.exec("test.name");
    // returns "456"
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.