Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nashorn in Java 11 behaves differently from Java 8 when evaluating named functions

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.6
Warning: 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).

like image 977
Joel Westberg Avatar asked Dec 01 '25 02:12

Joel Westberg


1 Answers

Probably caused by an issue with Nashorn's custom feature of anonymous function statements (these are actually called "function declarations") accidentally also applying to real named function declarations. Since this is not described in Nashorn docs I assume they didn't want that behavior and got rid of it.

Solution:

Transform the source code so that as its final act, it yields the function object that was defined by your last named function declaration.

Consider my tests to see that this will work in OpenJDK 8 and 11:

nashorn @ 1.8.0_302:

jjs> function bar() { foo(); }; function foo() { bar(); }     
function foo() { bar(); }

jjs> function bar() { foo(); }; function foo() { bar(); }; foo
function foo() { bar(); }

nashorn @ 11.0.6:

jjs> function bar() { foo(); }; function foo() { bar(); }

jjs> function bar() { foo(); }; function foo() { bar(); }; foo
function foo() { bar(); }

To figure out what name to use, you should be able to parse the JS and process its AST using the jdk.nashorn.api.tree package.

Your tree visitor / function name accumulator might look like:

private static class FuncDeclarationVisitor extends SimpleTreeVisitorES5_1<Void, Void> {
    public String lastFunctionName = null;

    @Override
    public Void visitFunctionDeclaration(FunctionDeclarationTree node, Void param) {
        // Anonymous function declaration ==> null
        IdentifierTree functionName = node.getName();
        lastFunctionName = functionName != null ? functionName.getName() : null;
        return super.visitFunctionDeclaration(node, param);
    }
}

You might invoke it like:

CompilationUnitTree parsedTree;     // Use Parser's parse method

FuncDeclarationVisitor visitor = new FuncDeclarationVisitor();
parsedTree.accept(visitor);

return visitor.lastFunctionName;     // Null if the last function declaration was anonymous

But I don't think there's any way to modify the AST and then send it to the NashornScriptEngine's compiler. You probably have to add something to the the source text itself.

Also, you probably also want your visitor to detect other types of nodes that are not function declarations, so that you don't accidentally transform the script and clobber other expressions that might be yielded (non-functions).

like image 143
AndrewF Avatar answered Dec 02 '25 16:12

AndrewF