Skip to content

Limits and safety

Running someone else's script means it might not stop. This page is about that.

Stop a script that will not return

KiteJs { instructionBudget = 5_000_000 }.use { js ->
    js.evaluate("while (true) {}")   // throws JsEngineError
}

The engine counts interpreter instructions. When one call passes the budget it gives up and throws JsEngineError. Each call gets the budget again, so a long session is not punished for what an earlier call did.

There is no right number for every case. Measure your own scripts and leave room:

Script Roughly
An arithmetic expression under 1,000
A loop over a hundred items with a callback tens of thousands
Parsing and transforming a large JSON document millions

Zero, the default, means no limit.

Stop it on your own signal

val deadline = TimeSource.Monotonic.markNow() + 2.seconds

KiteJs { interruptWhen = { deadline.hasPassedNow() } }.use { js ->
    js.evaluate(untrustedScript)
}

The hook is asked from inside the running script every hundred thousand instructions or so. Answer true and the script stops. Setting the hook turns the check on even with no budget.

Use this for a deadline, a stop button, or a cancelled coroutine. asyncKiteJs uses it for exactly that, and it chains: whatever you set is still asked.

Take away what a sandbox does not need

KiteJs {
    safeBuiltins = true    // leaves out the built-ins a sandbox does not want
    sealBuiltins = true    // a script cannot replace Array.prototype.push
    console = null         // no console at all
}

Sealing matters more than it sounds. Without it, one script can redefine Array.prototype.push and the next script in the same engine gets the redefined one.

Memory

There is no memory budget. A script that builds an ever larger array will exhaust the heap, and the engine cannot stop it. If that is a real risk for you, run the engine in a process or a worker you can kill.

Weak collections are the one place memory behaves differently by target:

Target WeakMap and WeakSet
JVM, Android, iOS, macOS, Linux, Windows Entries are released when the key is collected
JavaScript Same, on any runtime with WeakRef, which is every current one
WebAssembly Entries are held until the map is dropped

No script can tell the difference, because a WeakMap has no iteration and no size. The cost on WebAssembly is memory, not behaviour. WeakRef.isWeakSupported answers false there if your host code needs to know.

Speed

The engine interprets. It does not compile to bytecode or machine code, because iOS does not allow that and the port keeps one behaviour everywhere.

Measured against Mozilla Rhino's own interpreter on the JVM, KiteJS runs the same scripts about 1.0 to 2.0 times slower depending on the work: parsing is at parity, array and JSON methods are within 20 percent, and a tight arithmetic loop is about twice as slow.

For scripting a document, evaluating a rule, or transforming data, that is not the part of your program you will be waiting on. For a numeric inner loop over millions of iterations, write it in Kotlin and bind it.

One engine per thread

An engine belongs to the thread that opened it. Opening a second engine on that same thread throws a JsEngineError that says so, rather than quietly sharing the first one's state.

Each thread has its own slot, so two threads can hold two engines at once, and neither sees the other's global scope. A program that runs several documents gives each document a thread.

val worker = thread { KiteJs().use { js -> js.evaluate(script) } }

JavaScript and WebAssembly run one thread, so there one engine is open at a time.

Use asyncKiteJs when you want the calls serialised for you on a dispatcher of its own.

Errors from a script cannot break the engine

A script that throws leaves the engine usable. So does one that runs out of budget.

KiteJs { instructionBudget = 100_000 }.use { js ->
    runCatching { js.evaluate("for (;;) {}") }
    js.evaluate("1 + 2").asInt()   // 3, the engine is fine
}

Global state the script already changed is still changed. If you need a clean slate, build a new engine.