From 5e5fa9fcf1f971ba73dbc2965d81585f930327d7 Mon Sep 17 00:00:00 2001 From: andreatp Date: Thu, 14 May 2026 11:18:55 +0100 Subject: [PATCH] Fix UTF-8 encoding in writeResult for multi-byte characters (#176) writeResult used String.length() (Java char count) for malloc and opaJsonParse, which undercounts for multi-byte UTF-8 characters like em-dash (U+2014). Encode to UTF-8 bytes first and use byte length. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../main/java/com/styra/opa/wasm/OpaWasm.java | 8 ++-- .../java/com/styra/opa/wasm/Issue176Test.java | 38 +++++++++++++++++++ .../resources/fixtures/issue176/policy.rego | 5 +++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 core/src/test/java/com/styra/opa/wasm/Issue176Test.java create mode 100644 core/src/test/resources/fixtures/issue176/policy.rego diff --git a/core/src/main/java/com/styra/opa/wasm/OpaWasm.java b/core/src/main/java/com/styra/opa/wasm/OpaWasm.java index fe547dd..1ec2e96 100644 --- a/core/src/main/java/com/styra/opa/wasm/OpaWasm.java +++ b/core/src/main/java/com/styra/opa/wasm/OpaWasm.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.styra.opa.wasm.builtins.Provided; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -195,9 +196,10 @@ public String readString(int addr) { } public int writeResult(String result) { - var resultStrAddr = exports.opaMalloc(result.length()); - memory().writeCString(resultStrAddr, result); - var resultAddr = exports.opaJsonParse(resultStrAddr, result.length()); + var bytes = result.getBytes(StandardCharsets.UTF_8); + var resultStrAddr = exports.opaMalloc(bytes.length); + memory().write(resultStrAddr, bytes); + var resultAddr = exports.opaJsonParse(resultStrAddr, bytes.length); exports.opaFree(resultStrAddr); return resultAddr; } diff --git a/core/src/test/java/com/styra/opa/wasm/Issue176Test.java b/core/src/test/java/com/styra/opa/wasm/Issue176Test.java new file mode 100644 index 0000000..180fddb --- /dev/null +++ b/core/src/test/java/com/styra/opa/wasm/Issue176Test.java @@ -0,0 +1,38 @@ +package com.styra.opa.wasm; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class Issue176Test { + static Path wasmFile; + + @BeforeAll + public static void beforeAll() throws Exception { + wasmFile = + OpaCli.compile("issue176", "issue176/emdash_sprintf", "issue176/emdash_literal") + .resolve("policy.wasm"); + } + + @Test + public void emdashSprintf() { + var opa = OpaPolicy.builder().withPolicy(wasmFile).build(); + + var result = Utils.getResult(opa.entrypoint("issue176/emdash_sprintf").evaluate()); + + assertEquals( + "requested String value is invalid — please use one of the allowed values", + result.asText()); + } + + @Test + public void emdashLiteral() { + var opa = OpaPolicy.builder().withPolicy(wasmFile).build(); + + var result = Utils.getResult(opa.entrypoint("issue176/emdash_literal").evaluate()); + + assertEquals("This contains an em-dash — in a string literal", result.asText()); + } +} diff --git a/core/src/test/resources/fixtures/issue176/policy.rego b/core/src/test/resources/fixtures/issue176/policy.rego new file mode 100644 index 0000000..2dbc09b --- /dev/null +++ b/core/src/test/resources/fixtures/issue176/policy.rego @@ -0,0 +1,5 @@ +package issue176 + +emdash_sprintf := sprintf("requested %s value is invalid — please use one of the allowed values", ["String"]) + +emdash_literal := "This contains an em-dash — in a string literal"