Skip to content

Commit 6a4be2f

Browse files
committed
Add tests for Wasm exceptions interop
1 parent c0c2976 commit 6a4be2f

File tree

3 files changed

+122
-16
lines changed

3 files changed

+122
-16
lines changed

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.nio.ByteBuffer;
4747
import java.nio.ByteOrder;
4848
import java.util.Arrays;
49+
import java.util.EnumSet;
4950
import java.util.HashMap;
5051
import java.util.Objects;
5152
import java.util.concurrent.Callable;
@@ -79,9 +80,11 @@
7980
import org.graalvm.wasm.constants.Sizes;
8081
import org.graalvm.wasm.exception.WasmException;
8182
import org.graalvm.wasm.exception.WasmJsApiException;
83+
import org.graalvm.wasm.exception.WasmRuntimeException;
8284
import org.graalvm.wasm.globals.WasmGlobal;
8385
import org.graalvm.wasm.memory.WasmMemory;
8486
import org.graalvm.wasm.memory.WasmMemoryLibrary;
87+
import org.graalvm.wasm.utils.WasmBinaryTools;
8588
import org.junit.Assert;
8689
import org.junit.Test;
8790

@@ -101,6 +104,7 @@
101104

102105
public class WasmJsApiSuite {
103106
private static final String REF_TYPES_OPTION = "wasm.BulkMemoryAndRefTypes";
107+
private static final String EXCEPTIONS_OPTION = "wasm.Exceptions";
104108

105109
private static WasmFunctionInstance createWasmFunctionInstance(WasmContext context, int[] paramTypes, int[] resultTypes, RootNode functionRootNode) {
106110
WasmModule module = WasmModule.createBuiltin("dummyModule");
@@ -2514,6 +2518,121 @@ public void testUncachedIndirectCallException() throws IOException, InterruptedE
25142518
});
25152519
}
25162520

2521+
/**
2522+
* Tests that an exception thrown by WebAssembly can be caught in the embedder. Checks that the
2523+
* correct tag and exception fields are present.
2524+
*/
2525+
@Test
2526+
public void testExceptionFromInsideWasm() throws IOException, InterruptedException {
2527+
final byte[] binary = compileWat("exceptions-inside", """
2528+
(module
2529+
(tag $E (export "E") (param i32 externref))
2530+
(func (export "raise") (param i32 externref)
2531+
(throw $E (local.get 0) (local.get 1))
2532+
)
2533+
)""", EnumSet.of(WasmBinaryTools.WabtOption.EXCEPTIONS));
2534+
runTest(builder -> builder.option(EXCEPTIONS_OPTION, "true"), context -> {
2535+
final WebAssembly wasm = new WebAssembly(context);
2536+
final WasmInstance instance = moduleInstantiate(wasm, binary, null);
2537+
final Object raise = WebAssembly.instanceExport(instance, "raise");
2538+
final Object tag = WebAssembly.instanceExport(instance, "E");
2539+
2540+
final Object extern = "foo";
2541+
2542+
try {
2543+
try {
2544+
// Trigger the exception with fields: 7 (i32) and extern (externref)
2545+
InteropLibrary.getUncached(raise).execute(raise, 7, extern);
2546+
Assert.fail("Expected a WebAssembly exception but none was thrown.");
2547+
} catch (WasmRuntimeException ex) {
2548+
InteropLibrary exInterop = InteropLibrary.getUncached(ex);
2549+
// Verify it's an interop exception
2550+
Assert.assertTrue("Should be an interop exception", exInterop.isException(ex));
2551+
2552+
// Verify the tag
2553+
final Object exnTag = WebAssembly.exnTag(new Object[]{ex});
2554+
Assert.assertEquals("Unexpected exception tag", tag, exnTag);
2555+
2556+
// Verify the fields
2557+
Assert.assertTrue("Wasm exception should have fields as array elements", exInterop.hasArrayElements(ex));
2558+
Assert.assertEquals("Expected 2 exception fields", 2, exInterop.getArraySize(ex));
2559+
Assert.assertEquals("First field mismatch", 7, InteropLibrary.getUncached().asInt(exInterop.readArrayElement(ex, 0)));
2560+
Assert.assertSame("Second field mismatch", extern, exInterop.readArrayElement(ex, 1));
2561+
}
2562+
} catch (InteropException e) {
2563+
throw new AssertionError(e);
2564+
}
2565+
});
2566+
}
2567+
2568+
/**
2569+
* Tests that GraalWasm can catch wasm exceptions instantiated and thrown by the host. Also
2570+
* checks that the correct tag and exception fields are present.
2571+
*/
2572+
@Test
2573+
public void testExceptionFromOutsideWasm() throws IOException, InterruptedException {
2574+
// 1) Module that defines and exports the tag.
2575+
final byte[] tagModule = compileWat("exceptions-tag", """
2576+
(module
2577+
(tag $E (export "E") (param i32 externref))
2578+
)""", EnumSet.of(WasmBinaryTools.WabtOption.EXCEPTIONS));
2579+
2580+
// 2) Module that imports the tag and a host function. It calls the host function
2581+
// and catches exceptions with the imported tag, returning the exception fields.
2582+
final byte[] consumerModule = compileWat("exceptions-outside-consumer", """
2583+
(module
2584+
(type $host_t (func))
2585+
(tag $E (import "m" "E") (param i32 externref))
2586+
(func $host (import "m" "host") (type $host_t))
2587+
(func (export "callAndHandle") (result i32 externref)
2588+
(block $h (result i32 externref)
2589+
(try_table (result i32 externref) (catch $E $h)
2590+
(call $host)
2591+
;; In case the host does not throw, produce dummy values (won't be reached in this test).
2592+
(i32.const -1)
2593+
(ref.null extern)
2594+
)
2595+
)
2596+
)
2597+
)""", EnumSet.of(WasmBinaryTools.WabtOption.EXCEPTIONS));
2598+
2599+
runTest(builder -> builder.option(EXCEPTIONS_OPTION, "true"), context -> {
2600+
final WebAssembly wasm = new WebAssembly(context);
2601+
2602+
// Instantiate the tag-defining module and obtain the tag object.
2603+
final WasmInstance tagModuleInstance = moduleInstantiate(wasm, tagModule, null);
2604+
final Object tag = WebAssembly.instanceExport(tagModuleInstance, "E");
2605+
2606+
final Object extern = "foo";
2607+
2608+
// Import object providing the tag and the throwing host callback.
2609+
final Dictionary importObject = Dictionary.create(new Object[]{
2610+
"m", Dictionary.create(new Object[]{
2611+
"E", tag,
2612+
"host", new Executable(args -> {
2613+
throw wasm.exnAlloc(new Object[]{tag, 7, extern});
2614+
})
2615+
}),
2616+
});
2617+
2618+
// Instantiate the consumer and call its function that catches our exception.
2619+
final WasmInstance consumer = moduleInstantiate(wasm, consumerModule, importObject);
2620+
final Object callAndHandle = WebAssembly.instanceExport(consumer, "callAndHandle");
2621+
try {
2622+
final InteropLibrary interop = InteropLibrary.getUncached();
2623+
final Object result = interop.execute(callAndHandle);
2624+
2625+
// The catch $E returns the fields; verify them.
2626+
Assert.assertTrue("Result must be a multi-value array", interop.hasArrayElements(result));
2627+
Assert.assertEquals("Expected 2 results", 2, interop.getArraySize(result));
2628+
Assert.assertEquals("First field mismatch", 7, interop.asInt(interop.readArrayElement(result, 0)));
2629+
Assert.assertSame("Second field mismatch", extern, interop.readArrayElement(result, 1));
2630+
} catch (InteropException e) {
2631+
throw new AssertionError(e);
2632+
}
2633+
});
2634+
}
2635+
25172636
private static void runMemoryTest(Consumer<WasmContext> testCase) throws IOException {
25182637
runTest(null, testCase);
25192638
runTest(options -> options.option("wasm.UseUnsafeMemory", "true"), testCase);

wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/bytecode/MultiInstantiationSuite.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,8 @@ public void testImportsAndExports() throws IOException, InterruptedException {
237237
final Object eInstanceTag = lib.execute(exnTag, eInstance);
238238
Assert.assertSame("Exception tag does not match", e, eInstanceTag);
239239

240-
final Object exnRead = wasm.readMember("exn_read");
241-
final Object eInstanceFields = lib.execute(exnRead, eInstance);
242-
Assert.assertTrue("Exception fields is not an array", lib.hasArrayElements(eInstanceFields));
243-
Assert.assertEquals("Exception fields array size", 0, lib.getArraySize(eInstanceFields));
240+
Assert.assertTrue("Exception does not have fields", lib.hasArrayElements(eInstance));
241+
Assert.assertEquals("Exception fields count", 0, lib.getArraySize(eInstance));
244242

245243
final Object test = WebAssembly.instanceExport(i, "test");
246244
final int result = lib.asInt(lib.execute(test));

wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ public WebAssembly(WasmContext currentContext) {
121121

122122
addMember("exn_alloc", new Executable(this::exnAlloc));
123123
addMember("exn_tag", new Executable(WebAssembly::exnTag));
124-
addMember("exn_read", new Executable(WebAssembly::exnRead));
125124

126125
addMember("module_imports", new Executable(WebAssembly::moduleImports));
127126
addMember("module_exports", new Executable(WebAssembly::moduleExports));
@@ -882,7 +881,7 @@ public static Object tagType(Object[] args) {
882881
return tag.type().toString();
883882
}
884883

885-
public Object exnAlloc(Object[] args) {
884+
public WasmRuntimeException exnAlloc(Object[] args) {
886885
checkArgumentCount(args, 1);
887886
if (!(args[0] instanceof WasmTag tag)) {
888887
throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm tag");
@@ -914,16 +913,6 @@ public static Object exnTag(Object[] args) {
914913
return exn.tag();
915914
}
916915

917-
public static Object exnRead(Object[] args) {
918-
checkArgumentCount(args, 1);
919-
if (!(args[0] instanceof WasmRuntimeException exn)) {
920-
throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be a wasm exception");
921-
}
922-
// Should return exn.fields.
923-
// WasmRuntimeException already exposes its fields as array elements.
924-
return exn;
925-
}
926-
927916
private static Object instanceExport(Object[] args) {
928917
checkArgumentCount(args, 2);
929918
if (!(args[0] instanceof WasmInstance instance)) {

0 commit comments

Comments
 (0)