I have a Java application that allows users to manipulate certain objects at runtime by defining a JavaScript function. We are currently doing this with Nashorn in Java 8, but we are looking to move to Java 11. Once we're on Java 11 we'll be able to offer this functionality in GraalVM instead, but for now we need to maintain compatibility for Java 8 -> Java 11 upgrade of Nashorn scripts.
In Java 11, the behavior of Nashorn when we eval the function appears to differ depending on whether or not the function is named, which was not the case in Java 8. Here's an example using JJS in Java 11:
$ jjs -v
nashorn 11.0.6Warning: The jjs tool is planned to be removed from a future JDK release
jjs> function foo() {}
jjs> function () {}
function () {}
Note that the first function definition returns nothing. In Java 8 it does return the function even when the function is named:
$ jjs -v
nashorn 1.8.0_252
jjs> function foo() {}
function foo() {}
The way we invoke these scripts currently is through:
CompiledScript compiled = scriptEngine.compile(userProvidedScript);
Object evaled = compiled.eval(bindings);
scriptEngine.invokeMethod(evaled, "call", evaled, ... input parameters ...)
Curious if anyone knows the root cause for this and any good workarounds? I need to support function(...) as well as function foo(...) for back-compat reasons. Since this is done inside our Java application we could potentially wrap the user supplied script somehow, or try to grab the script out of the bindings (which seems error prone, since there can be multiple scripts defined, and the Java 8 behavior would be for the last defined script to be invoked).
It looks like the behavior of Nashorn changed in Java 11 when it comes to function definitions. In Java 8, defining a function would always return the function object, regardless of whether it was named or anonymous. In Java 11, defining a named function returns nothing, while defining an anonymous function returns the function object.
One workaround you could consider is to wrap the user-provided script in a self-executing anonymous function, which would ensure that the function object is returned in both Java 8 and Java 11:
(function() { // User-provided script goes here })();
This would allow you to continue using the existing code for invoking the function, since the result of eval will always be a function object.
Alternatively, you could modify your code to handle the different behaviors of Nashorn in Java 8 and Java 11. For example, you could check the type of the result of eval and only invoke the function if it is a function object:
CompiledScript compiled = scriptEngine.compile(userProvidedScript); Object evaled = compiled.eval(bindings); if (evaled instanceof Function) { scriptEngine.invokeMethod(evaled, "call", evaled, ... input parameters ...); }
This would allow you to support both named and anonymous functions in both Java 8 and Java 11.