From 3d2bcdaf6f519525c3d80dbe5dbd528148ad2462 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 5 Mar 2026 12:31:05 +0000 Subject: [PATCH 1/3] Don't rely on UTF-8 conversions --- .../quickjs4j/core/BasicScriptCache.java | 17 +++--- .../io/roastedroot/quickjs4j/core/Engine.java | 55 +++++++++++-------- .../io/roastedroot/quickjs4j/core/Runner.java | 7 ++- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/io/roastedroot/quickjs4j/core/BasicScriptCache.java b/core/src/main/java/io/roastedroot/quickjs4j/core/BasicScriptCache.java index 9af0196..30b91da 100644 --- a/core/src/main/java/io/roastedroot/quickjs4j/core/BasicScriptCache.java +++ b/core/src/main/java/io/roastedroot/quickjs4j/core/BasicScriptCache.java @@ -1,6 +1,6 @@ package io.roastedroot.quickjs4j.core; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; @@ -8,7 +8,7 @@ public class BasicScriptCache implements ScriptCache, AutoCloseable { private static final String DEFAULT_MESSAGE_DIGEST_ALGORITHM = "SHA-256"; - private final HashMap cache; + private final HashMap cache; private final MessageDigest messageDigest; public BasicScriptCache() { @@ -24,19 +24,20 @@ public BasicScriptCache(String messageDigestAlgorithm) { } } + private ByteBuffer key(byte[] code) { + return ByteBuffer.wrap(messageDigest.digest(code)); + } + public boolean exists(byte[] code) { - var key = messageDigest.digest(code); - return cache.containsKey(new String(key, StandardCharsets.UTF_8)); + return cache.containsKey(key(code)); } public void set(byte[] code, byte[] compiled) { - var key = messageDigest.digest(code); - cache.put(new String(key, StandardCharsets.UTF_8), compiled); + cache.put(key(code), compiled); } public byte[] get(byte[] code) { - var key = messageDigest.digest(code); - return cache.get(new String(key, StandardCharsets.UTF_8)); + return cache.get(key(code)); } public void close() { diff --git a/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java b/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java index e91ec1d..9c15f00 100644 --- a/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java +++ b/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java @@ -29,6 +29,7 @@ @WasmModuleInterface(WasmResource.absoluteFile) public final class Engine implements AutoCloseable { private static final int ALIGNMENT = 1; + private static final byte[] SEMICOLON_NL = ";\n".getBytes(UTF_8); public static final ObjectMapper DEFAULT_OBJECT_MAPPER = new ObjectMapper(); private final ByteArrayOutputStream stdout; @@ -120,11 +121,16 @@ private String readJavyString(int ptr, int len) { public Object invokeGuestFunction( String moduleName, String name, List args, String libraryCode) { + return invokeGuestFunction(moduleName, name, args, libraryCode.getBytes(UTF_8)); + } + + public Object invokeGuestFunction( + String moduleName, String name, List args, byte[] libraryCode) { return invokePrecompiledGuestFunction( moduleName, name, args, compilePortableGuestFunction(libraryCode)); } - public String invokeFunction() { + public byte[] invokeFunction() { var funInvoke = "globalThis[quickjs4j_engine.module_name()][quickjs4j_engine.function_name()](...JSON.parse(quickjs4j_engine.args()))"; Function setResult = @@ -135,11 +141,12 @@ public String invokeFunction() { + value + "]))"; - return "Promise.resolve(" - + funInvoke - + ").then((value) => { " - + setResult.apply("value") - + " }, (err) => { throw err; })"; + return ("Promise.resolve(" + + funInvoke + + ").then((value) => { " + + setResult.apply("value") + + " }, (err) => { throw err; })") + .getBytes(UTF_8); } // Plan: @@ -147,23 +154,23 @@ public String invokeFunction() { // protocol // { moduleName: "..", functionName: "..", args: "stringified args" } public byte[] compilePortableGuestFunction(String libraryCode) { + return compilePortableGuestFunction(libraryCode.getBytes(UTF_8)); + } + + public byte[] compilePortableGuestFunction(byte[] libraryCode) { int codePtr = 0; try { - var invokeFunction = invokeFunction(); - - String jsCode = - new String(jsPrelude(), UTF_8) - + "\n" - + libraryCode - + "\n" - + new String(jsSuffix(), UTF_8) - + "\n" - + invokeFunction - + ";\n"; - - // System.out.println(jsCode); - - codePtr = compileRaw(jsCode.getBytes(UTF_8)); + var buf = new ByteArrayOutputStream(); + buf.writeBytes(jsPrelude()); + buf.write('\n'); + buf.writeBytes(libraryCode); + buf.write('\n'); + buf.writeBytes(jsSuffix()); + buf.write('\n'); + buf.writeBytes(invokeFunction()); + buf.writeBytes(SEMICOLON_NL); + + codePtr = compileRaw(buf.toByteArray()); return readCompiled(codePtr); } finally { if (codePtr != 0) { @@ -271,7 +278,7 @@ private long[] invokeBuiltin(Instance instance, long[] args) { (returnType == Void.class) ? "null" : mapper.writerFor(returnType).writeValueAsString(res); - var returnBytes = returnStr.getBytes(); + var returnBytes = returnStr.getBytes(UTF_8); var returnPtr = exports.canonicalAbiRealloc( @@ -332,7 +339,7 @@ private byte[] jsPrelude() { + "\", JSON.stringify(args))) };\n"); } } - return preludeBuilder.toString().getBytes(); + return preludeBuilder.toString().getBytes(UTF_8); } // This function dynamically generates the js handlers for Invokables @@ -352,7 +359,7 @@ private byte[] jsSuffix() { + ";\n"); } } - return suffixBuilder.toString().getBytes(); + return suffixBuilder.toString().getBytes(UTF_8); } public int compile(String js) { diff --git a/core/src/main/java/io/roastedroot/quickjs4j/core/Runner.java b/core/src/main/java/io/roastedroot/quickjs4j/core/Runner.java index 81f0605..b6b2e79 100644 --- a/core/src/main/java/io/roastedroot/quickjs4j/core/Runner.java +++ b/core/src/main/java/io/roastedroot/quickjs4j/core/Runner.java @@ -24,10 +24,13 @@ private Runner(Engine engine, int timeout, int compilationTimeout, ExecutorServi } public byte[] compile(String code) { + return compile(code.getBytes(StandardCharsets.UTF_8)); + } + + public byte[] compile(byte[] code) { return submitWithTimeout( () -> { - byte[] codeBytes = code.getBytes(StandardCharsets.UTF_8); - int codePtr = engine.compile(codeBytes); + int codePtr = engine.compile(code); try { return engine.readCompiled(codePtr); } finally { From 3565047b85a6dd219d431e7e0aee2ca099de9fc7 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 5 Mar 2026 12:36:28 +0000 Subject: [PATCH 2/3] review --- .../main/java/io/roastedroot/quickjs4j/core/Engine.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java b/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java index 9c15f00..b8fa19c 100644 --- a/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java +++ b/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java @@ -30,6 +30,7 @@ public final class Engine implements AutoCloseable { private static final int ALIGNMENT = 1; private static final byte[] SEMICOLON_NL = ";\n".getBytes(UTF_8); + private static final byte[] NULL_BYTES = "null".getBytes(UTF_8); public static final ObjectMapper DEFAULT_OBJECT_MAPPER = new ObjectMapper(); private final ByteArrayOutputStream stdout; @@ -274,11 +275,10 @@ private long[] invokeBuiltin(Instance instance, long[] args) { } } - var returnStr = + var returnBytes = (returnType == Void.class) - ? "null" - : mapper.writerFor(returnType).writeValueAsString(res); - var returnBytes = returnStr.getBytes(UTF_8); + ? NULL_BYTES + : mapper.writerFor(returnType).writeValueAsBytes(res); var returnPtr = exports.canonicalAbiRealloc( From 52891cc9cdc8f90c387bc3dd09bf832fd090bf04 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 5 Mar 2026 12:52:52 +0000 Subject: [PATCH 3/3] review --- .../io/roastedroot/quickjs4j/core/Engine.java | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java b/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java index b8fa19c..c02f606 100644 --- a/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java +++ b/core/src/main/java/io/roastedroot/quickjs4j/core/Engine.java @@ -29,7 +29,7 @@ @WasmModuleInterface(WasmResource.absoluteFile) public final class Engine implements AutoCloseable { private static final int ALIGNMENT = 1; - private static final byte[] SEMICOLON_NL = ";\n".getBytes(UTF_8); + private static final byte[] NULL_BYTES = "null".getBytes(UTF_8); public static final ObjectMapper DEFAULT_OBJECT_MAPPER = new ObjectMapper(); @@ -131,7 +131,7 @@ public Object invokeGuestFunction( moduleName, name, args, compilePortableGuestFunction(libraryCode)); } - public byte[] invokeFunction() { + private String invokeFunction() { var funInvoke = "globalThis[quickjs4j_engine.module_name()][quickjs4j_engine.function_name()](...JSON.parse(quickjs4j_engine.args()))"; Function setResult = @@ -142,36 +142,35 @@ public byte[] invokeFunction() { + value + "]))"; - return ("Promise.resolve(" - + funInvoke - + ").then((value) => { " - + setResult.apply("value") - + " }, (err) => { throw err; })") - .getBytes(UTF_8); + return "Promise.resolve(" + + funInvoke + + ").then((value) => { " + + setResult.apply("value") + + " }, (err) => { throw err; })"; } // Plan: // we compile a static version of the module and we invoke it parametrically using a basic // protocol // { moduleName: "..", functionName: "..", args: "stringified args" } - public byte[] compilePortableGuestFunction(String libraryCode) { - return compilePortableGuestFunction(libraryCode.getBytes(UTF_8)); + public byte[] compilePortableGuestFunction(byte[] libraryCode) { + return compilePortableGuestFunction(new String(libraryCode, UTF_8)); } - public byte[] compilePortableGuestFunction(byte[] libraryCode) { + public byte[] compilePortableGuestFunction(String libraryCode) { int codePtr = 0; try { - var buf = new ByteArrayOutputStream(); - buf.writeBytes(jsPrelude()); - buf.write('\n'); - buf.writeBytes(libraryCode); - buf.write('\n'); - buf.writeBytes(jsSuffix()); - buf.write('\n'); - buf.writeBytes(invokeFunction()); - buf.writeBytes(SEMICOLON_NL); - - codePtr = compileRaw(buf.toByteArray()); + var buf = new StringBuilder(); + buf.append(jsPrelude()); + buf.append('\n'); + buf.append(libraryCode); + buf.append('\n'); + buf.append(jsSuffix()); + buf.append('\n'); + buf.append(invokeFunction()); + buf.append(";\n"); + + codePtr = compileRaw(buf.toString().getBytes(UTF_8)); return readCompiled(codePtr); } finally { if (codePtr != 0) { @@ -322,7 +321,7 @@ private long[] invokeBuiltin(Instance instance, long[] args) { this::invokeBuiltin); // This function dynamically generates the global functions defined by the Builtins - private byte[] jsPrelude() { + private String jsPrelude() { var preludeBuilder = new StringBuilder(); for (Map.Entry builtin : builtins.entrySet()) { preludeBuilder.append("globalThis." + builtin.getKey() + " = {};\n"); @@ -339,11 +338,11 @@ private byte[] jsPrelude() { + "\", JSON.stringify(args))) };\n"); } } - return preludeBuilder.toString().getBytes(UTF_8); + return preludeBuilder.toString(); } // This function dynamically generates the js handlers for Invokables - private byte[] jsSuffix() { + private String jsSuffix() { var suffixBuilder = new StringBuilder(); for (Map.Entry invokable : invokables.entrySet()) { // The object is already defined by the set_result, just add the handlers @@ -359,7 +358,7 @@ private byte[] jsSuffix() { + ";\n"); } } - return suffixBuilder.toString().getBytes(UTF_8); + return suffixBuilder.toString(); } public int compile(String js) { @@ -367,7 +366,7 @@ public int compile(String js) { } public int compile(byte[] js) { - byte[] prelude = jsPrelude(); + byte[] prelude = jsPrelude().getBytes(UTF_8); byte[] jsCode = new byte[prelude.length + js.length]; System.arraycopy(prelude, 0, jsCode, 0, prelude.length); System.arraycopy(js, 0, jsCode, prelude.length, js.length);