Skip to content
Advertisement

Standalone Nashorn with Java 11 throws java.lang.StackOverflowError upon eval

I came across an issue with Nashorn, when evaluating a large expression, it works fine in Java 8 , but throws a java.lang.StackOverflowError in Java 11.

    Exception in thread "main" java.lang.StackOverflowError
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.enterBinaryNode(LocalVariableTypesCalculator.java:444)
        at org.openjdk.nashorn.internal.ir.BinaryNode.accept(BinaryNode.java:329)
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.enterJoinPredecessorExpression(LocalVariableTypesCalculator.java:777)
        at org.openjdk.nashorn.internal.ir.JoinPredecessorExpression.accept(JoinPredecessorExpression.java:114)
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.visitExpression(LocalVariableTypesCalculator.java:603)
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.enterBinaryNode(LocalVariableTypesCalculator.java:447)
        at org.openjdk.nashorn.internal.ir.BinaryNode.accept(BinaryNode.java:329)

I came across this question, and in an attempt to fix this issue, as suggested in this comment, I’m trying to use the Standalone Nashorn with Java 11, by using org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory in my code (See this page for info on “why do we need to do that”).

I’m having a simple Maven project, where I have added the following to my pom. For simplicity, I’ve included only the following important parts:

  • Nashorn dependency
  • Maven Compiler Plugin
     <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
        
            <groupId>org.example</groupId>
            <artifactId>maven-project</artifactId>
            <version>1.0-SNAPSHOT</version>
            <packaging>jar</packaging>
        
            <dependencies>
                <dependency>
                    <groupId>org.openjdk.nashorn</groupId>
                    <artifactId>nashorn-core</artifactId>
                    <version>15.3</version>
                </dependency>
            </dependencies>
        
            <build>
                <plugins>
                    ...
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.8.0</version>
                        <configuration>
                            <release>11</release>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        
        </project>

(Apart from these, I’m using maven-dependency-plugin and maven-jar-plugin to bundle this into an executable Jar).

The following is my Java code: Main.java

    package main;
    
    import org.openjdk.nashorn.api.scripting.NashornScriptEngine;
    import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
    
    import javax.script.ScriptEngine;
    
    public class Main {
    
        public static void main(String[] args) {
            // write your code here
            String expression = "("BUY" == "BUY") && ("BUY" == "BUY") && (1.00 >= 1000.000)"; // It's a long expression which contains conditions like this.
    
            try {
                NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
                System.out.println("================================================================================");
                System.out.println("factory.engineName: " + factory.getEngineName());
                System.out.println("factory.engineVersion: " + factory.getEngineVersion());
                System.out.println("factory.languageName: " + factory.getLanguageName());
                System.out.println("factory.languageVersion: " + factory.getLanguageVersion());
                System.out.println("================================================================================");
                ScriptEngine engine = factory.getScriptEngine();
    
                Object results = engine.eval(expression);
                System.out.println("Successful");
    
                System.out.println(results);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 

With Java version java version “11.0.9” 2020-10-20 LTS, I’m compiling a jar called maven-project-1.0-SNAPSHOT.jar via doing mvn clean install.

When running the main class of the Jar as follows:

    java -jar target/maven-project-1.0-SNAPSHOT.jar

Based on this information, the standalone version of Nashorn will be loaded with Java 11 since I have used org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory, and therefore, I’m not expecting a java.lang.StackOverflowError with Java 11.

But my output produces a StackOverflowError:

    ================================================================================
    factory.engineName: OpenJDK Nashorn
    factory.engineVersion: 15.3
    factory.languageName: ECMAScript
    factory.languageVersion: ECMA - 262 Edition 5.1
    ================================================================================
    Exception in thread "main" java.lang.StackOverflowError
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.enterJoinPredecessorExpression(LocalVariableTypesCalculator.java:775)
        at org.openjdk.nashorn.internal.ir.JoinPredecessorExpression.accept(JoinPredecessorExpression.java:114)
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.visitExpression(LocalVariableTypesCalculator.java:603)
        at org.openjdk.nashorn.internal.codegen.LocalVariableTypesCalculator.enterBinaryNode(LocalVariableTypesCalculator.java:447)
        at org.openjdk.nashorn.internal.ir.BinaryNode.accept(BinaryNode.java:329)



Update: When compiling this with Java 17 and testing, I did not get a StackOverflowError. As pointed out by @Thorbjørn Ravn Andersen in the comments, the reason to check this was, to rule out the possibility of the in-built Nashorn being picked in Java 11, instead of the standalone one. (Since Java 17 doesn’t have the inbuilt Nashorn, it would pick only the standalone one).

Based on this, I can also think of an addition to this question:

how can we make sure that the standalone Nashorn is picked up in Java 11?

I tried executing the jar as follows (referred to this answer of this related question), but the StackOverflowError is still being thrown. (The /path/to/my/maven-project/target/libs/ directory contains jars such as nashorn-core-15.3.jar, asm-7.3.1.jar, asm-commons-7.3.1.jar …)

java --module-path "/path/to/my/maven-project/target/libs/" --add-modules org.openjdk.nashorn -jar target/maven-project-1.0-SNAPSHOT.jar

(Instead of passing arguments/flags like this, a preferrable way would be to add these as a configuration or so in the pom – and handle it “neater”).


FYI, The following code works fine with Java 8, but throws the same StackOverflowError in Java 11, which is why I had to use org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory instead of javax.script.ScriptEngineManager.

    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    
    public class Main {
    
        public ScriptEngine engine = new ScriptEngineManager().getEngineByName(ENGINE_NAME);;
        public static final String ENGINE_NAME = "nashorn";
    
        public static void main(String[] args) {
            // write your code here
            Main main = new Main();
            Object result = null;
            String exp = "(1640 >= 10) && (1640 <= 100) && (1640 >= 123)";
            try {
                result = main.engine.eval(exp);
                System.out.println(result);
            } catch (ScriptException e) {
                e.printStackTrace();
            }
        }
    }

Am I missing anything here? Thanks in advance for any solutions.

Advertisement

Answer

How long is the actual source code expression that in the JavaScript code that causes this?

You are not getting the exception in Java 8 because Nashorn in Java 8 didn’t have some of the more advanced static type inference capabilities which allow it to generate more optimal code when it can prove some variables will always be ints or doubles and never objects.

This static type inference was added in a later Nashorn version, one released with Java 9. The stack overflow happens in its code as it needs to recursively walk the expression tree. If you have an extremely long expression, this might happen. I’d suggest you break very long expressions into smaller ones, either isolating them into local functions or assigning them to local variables. Local functions are better as then you can keep short-circuited evaluation, e.g. if you had

function f(a, b, c, d, e, f, g, h) {
  return a == b && c == d && e == f && g == h;
}

you can instead do

function f(a, b, c, d, e, f, g, h) {
  function p1() {
    return a == b && c == d;
  }
  function p2() {
    return e == f && g == h;
  }
 
  return p1() && p2();
}

This example just illustrates the technique, Nashorn should be good to evaluate very long expressions before running into a stack overflow.


Alternatively, run your JVM with explicit -Xss setting to give threads more stack memory.

8 People found this is helpful
Advertisement