javascriptjavagraalvmgraaljs

StackOverflowError when executing javascript function from java with graalvm


I know its a lot to ask to read such a big post but i hope you do as i really dont have any idea where to start looking for a solution of a problem that I dont even understand.

I have a java service on a windows server. This service loads/evaluates several javascript files at startup. Those javascript files all have the same structure, they contain two functions: process(name, content) { return { result: data } } and selfTest(). The server loads the script, evaluates it, checks for the reqiured functions, runs the selftest and stores the handle of the process function in a map (key for the map is the field name the script is executed on) if the selftest succeeds:

// Called on MainThread
private static final String JS_FN_SELFTEST = "selfTest";
private static final String JS_FN_PROCESS = "process";

private final Map<String, Value> _cache;
private final Context _ctx;

public ScriptManager () {
    _cache = new HashMap<>();
    _ctx = Context.newBuilder("js").allowIO(true).build();
}

private void addScriptFile(Path fileName, String... fieldNames)
            throws IOException, NoSuchMethodException    {
    var p = Path.of(fileName.toString());
    var jsSource = Files.readString(p);

    var evaluated = _ctx.eval("js", jsSource);
            
    if (!evaluated.hasMember(JS_FN_SELFTEST)) {
        throw new NoSuchMethodException(JS_FN_SELFTEST);
    }
    var fnSelfTest = evaluated.getMember(JS_FN_SELFTEST);
    fnSelfTest.execute();
    
    if (!evaluated.hasMember(JS_FN_PROCESS)) {
        throw new NoSuchMethodException(JS_FN_PROCESS);
    }
    
    for(String column: fieldNames) {
        _cache.put(column, evaluated.getMember(JS_FN_PROCESS));
    }
}

After startup the service watches a network share for file drops. when a file drops, the file will be read and processed. The data will be cleaned first with a generic script and then the fields that are part of the map will also be processed. The following function does the cleaning, the field-processing is similar and the problem occurs there too.

// Called on JobProcessingThread
public List<Data> clean(DataMap dataSet)  {
    var modified = new ArrayList<Data>();
    var fnProcess = _cache.get(ALL_COLUMNS_MARKER);
    
    for(var key: dataSet.keyList()) {
        var data = dataSet.get(key);
        if (!data.Field.Scriptable) continue;

        String oldContent = data.getContent();
        if (oldContent == null) continue;

        try {
            var value = fnProcess.execute(data.SourceName, oldContent); // error occurs here
            var map = value.as(Map.class);
            var newContent = map.get("result").toString();

            if (!oldContent.equals(newContent)) {
                data.setContent(newContent);
                modified.add(data);
            }
        } catch(PolyglotException pe)    {
            data.setProblem(pe.getMessage());
        }
    }
    
    return modified;
}

And finally this is one of the loaded scripts:

(function() {

function process(name, input) {
    if (input === null) return { result: null };
    
    let tmp = input.trim();
    if (tmp.length === 0) return { result: null };
    
    // some logic here
    
    return { 
        result: result !== '' ? result : null
    };
}

function selfTest() {
}

return { process, selfTest };
})();

Thats the scenario. When I run the app and drop a file I receive a StackOverflowError when the first scriptable field is processed in line fnProcess.execute(data.SourceName, oldContent); and The JVM is terminated (!). The script debugger of netbeans shows that this happens in the line let tmp = input.trim();. When i remove the trim() command the script will be executed until the next function call on a string (like indexOf) but the length check tmp.length === 0 will be executed normally (at least the netbeans debugger shows it like that). The problem occurs regardless of the string content passed, even hard coding strings generates the error in the server environment.

Uncaught exception exiting JVMCIEnv scope entered at c:\buildbot\worker\e\main\jdk\src\hotspot\share\jvmci\jvmciCompilerToVM.cpp:2386
Exception in thread "Thread-JobCenter": java.lang.StackOverflowError
java.lang.StackOverflowError
    at com.oracle.svm.core.graal.snippets.StackOverflowCheckSnippets.newStackOverflowError0(StackOverflowCheckImpl.java:363)
    at com.oracle.svm.core.graal.snippets.StackOverflowCheckSnippets.newStackOverflowError(StackOverflowCheckImpl.java:359)
    at com.oracle.svm.core.graal.snippets.StackOverflowCheckSnippets.throwNewStackOverflowError(StackOverflowCheckImpl.java:339)
    at com.oracle.svm.core.graal.jdk.ArraycopySnippets.boundsCheck(ArraycopySnippets.java:117)
    at com.oracle.svm.core.graal.jdk.ArraycopySnippets.doArraycopy(ArraycopySnippets.java:94)
    at java.util.Arrays.copyOf(Arrays.java:3539)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:228)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:802)
    at java.lang.StringBuilder.append(StringBuilder.java:242)
    at com.oracle.svm.core.util.Utf8.utf8ToString(Utf8.java:144)
    at com.oracle.svm.core.util.Utf8.utf8ToString(Utf8.java:187)
    at com.oracle.svm.jni.functions.JNIFunctions.NewStringUTF(JNIFunctions.java:479)
    at jdk.vm.ci.hotspot.CompilerToVM.getResolvedJavaType0(CompilerToVM.java)
    at jdk.vm.ci.hotspot.CompilerToVM.getResolvedJavaType(CompilerToVM.java:674)
    at jdk.vm.ci.hotspot.HotSpotResolvedJavaMethodImpl.getHolder(HotSpotResolvedJavaMethodImpl.java:97)
    at jdk.vm.ci.hotspot.HotSpotResolvedJavaMethodImpl.fromMetaspace(HotSpotResolvedJavaMethodImpl.java:115)
    at com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VA_LIST:Ljdk_vm_ci_hotspot_HotSpotResolvedJavaMethodImpl_2_0002efromMetaspace_00028J_00029Ljdk_vm_ci_hotspot_HotSpotResolvedJavaMethod_2(JNIJavaCallWrappers.java:0)
    at org.graalvm.libgraal.jni.JNI$CallStaticObjectMethodA.call(JNI.java)
    at org.graalvm.libgraal.jni.FromLibGraalCalls.callJObject(FromLibGraalCalls.java:153)
    at org.graalvm.compiler.truffle.compiler.hotspot.libgraal.HSTruffleCompilerRuntimeGen.callGetTruffleCallBoundaryMethods(HSTruffleCompilerRuntimeGen.java:181)
    at org.graalvm.compiler.truffle.compiler.hotspot.libgraal.HSTruffleCompilerRuntime.getTruffleCallBoundaryMethods(HSTruffleCompilerRuntime.java:356)
    at org.graalvm.compiler.truffle.compiler.hotspot.HotSpotTruffleCompilerImpl.installTruffleCallBoundaryMethods(HotSpotTruffleCompilerImpl.java:216)
    at org.graalvm.compiler.truffle.compiler.hotspot.libgraal.TruffleToLibGraalEntryPoints.installTruffleCallBoundaryMethods(TruffleToLibGraalEntryPoints.java:305)
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (jvmciRuntime.cpp:1215), pid=15740, tid=10088
#  fatal error: Fatal exception in JVMCI: Uncaught exception exiting JVMCIEnv scope entered at c:\buildbot\worker\e\main\jdk\src\hotspot\share\jvmci\jvmciCompilerToVM.cpp:2386
#
# JRE version: OpenJDK Runtime Environment GraalVM CE 21.1.0 (16.0.1+9) (build 16.0.1+9-jvmci-21.1-b05)
# Java VM: OpenJDK 64-Bit Server VM GraalVM CE 21.1.0 (16.0.1+9-jvmci-21.1-b05, mixed mode, sharing, tiered, jvmci, jvmci compiler, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Data\hs_err_pid15740.log
#
# If you would like to submit a bug report, please visit:
#   https://github.com/oracle/graal/issues
#

This is part of the hs_err_pid15740.log

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  jdk.vm.ci.hotspot.CompilerToVM.translate(Ljava/lang/Object;)J+0 jdk.internal.vm.ci@16.0.1
j  jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.translate(Ljava/lang/Object;)J+5 jdk.internal.vm.ci@16.0.1
j  jdk.internal.reflect.GeneratedMethodAccessor4.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;+40 java.base@16.0.1
j  jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;+6 java.base@16.0.1
j  java.lang.reflect.Method.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;+59 java.base@16.0.1
j  org.graalvm.libgraal.LibGraal.translate(Ljava/lang/Object;)J+28 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.hotspot.libgraal.TruffleFromLibGraalEntryPoints.getTruffleCallBoundaryMethods(Ljava/lang/Object;)[J+122 jdk.internal.vm.compiler
v  ~StubRoutines::call_stub
j  org.graalvm.compiler.truffle.runtime.hotspot.libgraal.TruffleToLibGraalCalls.installTruffleCallBoundaryMethods(JJLorg/graalvm/compiler/truffle/common/CompilableTruffleAST;)V+0 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.hotspot.libgraal.LibGraalHotSpotTruffleCompiler.installTruffleCallBoundaryMethods(Lorg/graalvm/compiler/truffle/common/CompilableTruffleAST;)V+25 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.hotspot.AbstractHotSpotTruffleRuntime.bypassedInstalledCode(Lorg/graalvm/compiler/truffle/runtime/OptimizedCallTarget;)V+14 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.interpreterCall()Z+11 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callBoundary([Ljava/lang/Object;)Ljava/lang/Object;+1 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.doInvoke([Ljava/lang/Object;)Ljava/lang/Object;+2 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callBoundary([Ljava/lang/Object;)Ljava/lang/Object;+9 jdk.internal.vm.compiler
j  org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.doInvoke([Ljava/lang/Object;)Ljava/lang/Object;+2 jdk.internal.vm.compiler
// repeated for about 8000 rows...

But when I ran the following code with the same script file as above and i pass the same arguments to the javascript function as the server would do when the file drops, no exception is thrown:

var ctx = Context.newBuilder("js").allowAllAccess(false).allowIO(true).build();
var js = java.nio.file.Files.readString(Path.of("scripts/0.js"));
var evaluated = ctx.eval("js", js);
var fn = evaluated.getMember("process");
var result = fn.execute("test", "some content ");

I hope someone survived the gigantic textwall and made it till here and can tell me what i am doing wrong...


Solution

  • I could solve the problem by upgrading GraalVM to version 22.2. Sadly, with this version, javascript support is not included anymore and have to be added with the console. Still have no idea what was wrong (and I am almost sure that the problem did not exist in the previous version).

    This is not to credit myself. But because of the large text people might only look if there is an accepted answer and would not see an answer at the end of the text