ScriptEngine reusing arguments?

238 Views Asked by At

I've been running into weird issues with Kotlins JSR-223 implementation for ScriptEngine (kotlin-scripting-jsr223)

Basically, i have a condition, which i parse and compile into a CompiledScript:

fun parse(clazz: KClass<out GenericEvent>, condition: String): String {
    val simpleName = clazz.simpleName
    return """
        import ${clazz.java.name}
        
        val condition: $simpleName.() -> Boolean = { ${trimQuotes(condition)} }
        
        fun check(event: $simpleName) = condition.invoke(event)
            
    """.trimIndent()
}

I then compile the resulting string using (engine as Compilable).compile(code) into a CompiledScript.

Now, whenever i try to eval() these CompiledScripts multiple times in a very short period of time:

listener.compiledScript.eval()
return@Callable (engine as Invocable).invokeFunction("check", event)

It seems to reuse old previously used arguments. First i thought it was a thread-safety issue (Because it heavily looks like one), but using a Mutex or a single threaded executor to compile the script didn't change this behavior.

Full code:

override suspend fun onEvent(e: GenericEvent) {
    val events = listeners[e.javaClass] ?: return

    events.forEach { listener ->
        try {
            val event = e.javaClass.cast(e)
            if (listener.compiledScript != null) {
                val result = executor.submit(Callable { // executor = Executors.newSingleThreadExecutor()
                    println(Thread.currentThread().id) // confirms it's always the same thread
                    listener.compiledScript.eval()
                    return@Callable (engine as Invocable).invokeFunction("check", event)
                }).get()
                if (result is Boolean && result)
                    listener.method.callSuspend(listener.instance, event)
            } else {
                listener.method.callSuspend(listener.instance, event)
            }
        } catch (throwable: Throwable) {
            log.error("An error occurred while evaluating listener condition!", throwable)
        }
    }
}

At first, this works as expected

Condition: message.contentRaw == "first"
Is actually: first

but it creates weird, but always the same patterns afterwards.

Condition: message.contentRaw == "first"
Is actually: second

Condition: message.contentRaw == "second"
Is actually: second

Condition: message.contentRaw == "first"
Is actually: second

Condition: message.contentRaw == "second"
Is actually: second

Condition: message.contentRaw == "first"
Is actually: second

Condition: message.contentRaw == "second"
Is actually: second

(These messages shouldn't be printed if the condition isn't true)

I did some debugging earlier in which i printed the condition and the actual event in the compiled code itself, and it did reuse the previous event parameter in check(), even though i actually used an entirely different one into it.

1

There are 1 best solutions below

1
Xirado On

Fixed it by creating a new SimpleScriptContext, passing it into the eval() method of CompiledScript, and then calling ScriptEngine#setContext with the newly created context before calling invokeFunction()

Crazy stuff!