Function object cannot be called with different context

Frantzius, Jörg Joerg.Frantzius at aperto.com
Tue Jan 31 10:39:56 UTC 2017


Hi Hannes,

thanks for the explanations! The Javascript function being a closure over the scope it was evaluated in is exactly what I did not understand yet.

As the function object is dynamically created within some 3rd party Javascript that I cannot modify, I’m not able to create a CompiledScript instead.

Just for some nit-picking, my test was able to call the function without placing it in parenthesis (the error message was '"foo" is not defined‘, so execution did enter the function, even though it does not find „foo“ because that wasn't defined in its closure). So it seems that the function declaration does not evaluate to undefined, at least not when evaluating it using engine.eval() from Java.

Thanks again,
Jörg

Am 30.01.2017 um 18:52 schrieb Hannes Wallnöfer <hannes.wallnoefer at oracle.com<mailto:hannes.wallnoefer at oracle.com>>:

There are a few problems with this code. The first is that a function declaration evaluates as undefined in Javascript, so your jsFunction variable is null. One way to work around this is to make the function an expression, e.g. by wrapping it in parentheses:

engine.eval(„function test() { }“);  // returns null but defines function „test"
engine.eval(„(function test() { })“);  // returns the function but doesn’t define it in scope

Another issue is that Nashorn cannot directly use the default Binding implementation used by SimpleScriptContext. You should create a native Nashorn global object using engine.createBindings() as described in http://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/api.html

       ScriptContext newContext = new SimpleScriptContext();
       newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
       Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);

However, even with these issues fixed the problem remains that JavaScript functions are closures over the scope they were evaluated in. An evaluated function is always bound to the global object it was evaluated in. You can use the Compilable interface (as you did in your code) to create a compiled version of a script that will run with multiple globals:

   ScriptEngine engine =  new ScriptEngineManager().getEngineByName("JavaScript");
   CompiledScript compiledScript = ((Compilable) engine).compile("function test() {return foo;}; test();“);

   ScriptContext context = new SimpleScriptContext();
   context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
   context.setAttribute("foo", "bar", ScriptContext.ENGINE_SCOPE);
   Object result = compiledScript.eval(context);

Hannes


Am 30.01.2017 um 11:28 schrieb Frantzius, Jörg <Joerg.Frantzius at aperto.com<mailto:Joerg.Frantzius at aperto.com>>:

Hi,

in my code, I get hold of a function object from some 3rd party Javascript code, and I need to execute that function within a new context, i.e. make new ENGINE_SCOPE bindings available to it. ScriptObjectMirror.call() unfortunately does not allow to define a context to execute in, so I tried to workaround by calling the function from a CompiledScript that in turn can be executed with a custom context:

  @Test
  public void testJsFunctionWithContext() throws ScriptException {
      ScriptEngine engine =  new ScriptEngineManager().getEngineByName("JavaScript");
      jdk.nashorn.api.scripting.ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval("function test() {return foo;}");

      ScriptContext context = new SimpleScriptContext();
      context.setAttribute("foo", "bar", ScriptContext.ENGINE_SCOPE);
      context.setAttribute("fn", jsFunction, ScriptContext.ENGINE_SCOPE);

      // does work: access context directly
      CompiledScript contextWrapper = ((Compilable) engine).compile("foo");
      String result = (String) contextWrapper.eval(context);
      assertEquals("bar", result);

      // does not work: access context from within function ("foo" is not defined)
      contextWrapper = ((Compilable) engine).compile("fn()");
      result = (String) contextWrapper.eval(context);
      assertEquals("bar", result);
  }

Is there some mistake I made here, or is it simply not possible for a function object to access variables from a new context?
If that’s really not possible, is there maybe a way of turning a function object into a CompiledScript object, which can then be evaluated with a new context?

Thanks for any help,
Jörg



---

Dipl. Inf. Jörg von Frantzius, Technical Director

E-Mail joerg.frantzius at aperto.com<mailto:joerg.frantzius at aperto.com>

Phone +49 30 283921-318
Fax +49 30 283921-29

Aperto GmbH – An IBM Company
Chausseestraße 5, D-10115 Berlin
http://www.aperto.com<http://www.aperto.de/>
http://www.facebook.com/aperto
https://www.xing.com/companies/apertoag

HRB 77049 B, AG Berlin Charlottenburg
Geschäftsführer: Dirk Buddensiek, Kai Großmann, Stephan Haagen, Daniel Simon




---

Dipl. Inf. Jörg von Frantzius, Technical Director

E-Mail joerg.frantzius at aperto.com

Phone +49 30 283921-318
Fax +49 30 283921-29

Aperto GmbH – An IBM Company
Chausseestraße 5, D-10115 Berlin
http://www.aperto.com<http://www.aperto.de/>
http://www.facebook.com/aperto
https://www.xing.com/companies/apertoag

HRB 77049 B, AG Berlin Charlottenburg
Geschäftsführer: Dirk Buddensiek, Kai Großmann, Stephan Haagen, Daniel Simon



More information about the nashorn-dev mailing list