From 2e0864017b4fcf56bb102a35fe769d2a36adecaa Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 24 Oct 2024 20:05:57 +0200 Subject: [PATCH] wasm gc: improve exception stack trace when exception is thrown from wasm and rethrown between Java and JS --- .../teavm/classlib/java/lang/TThrowable.java | 4 ++ .../org/teavm/backend/wasm/WasmGCTarget.java | 40 +++++++++++++++++++ .../gc/classes/WasmGCClassGenerator.java | 11 +++++ .../gc/classes/WasmGCClassInfoProvider.java | 2 + core/src/main/js/wasm-gc-runtime/runtime.js | 17 ++++---- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java index 8c8b83fa5..09513937b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TThrowable.java @@ -111,10 +111,14 @@ public class TThrowable extends RuntimeException { stackTrace = (TStackTraceElement[]) (Object) ExceptionHandling.fillStackTrace(); } else if (PlatformDetector.isWebAssemblyGC()) { lazyStackTrace = takeWasmGCStack(); + decorateException(this); } return this; } + @Import(name = "decorateException") + private static native void decorateException(Object obj); + private void ensureStackTrace() { if (PlatformDetector.isWebAssemblyGC()) { if (lazyStackTrace != null) { diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 76e7c5dff..093d06221 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -46,8 +46,13 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactory; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsics; import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmGetLocal; +import org.teavm.backend.wasm.model.expression.WasmStructGet; +import org.teavm.backend.wasm.model.expression.WasmStructSet; import org.teavm.backend.wasm.optimization.WasmUsageCounter; import org.teavm.backend.wasm.render.WasmBinaryRenderer; import org.teavm.backend.wasm.render.WasmBinaryStatsCollector; @@ -279,11 +284,46 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { moduleGenerator.generate(); customGenerators.contributeToModule(module); + generateExceptionExports(declarationsGenerator); adjustModuleMemory(module); emitWasmFile(module, buildTarget, outputName, debugInfoBuilder); } + private void generateExceptionExports(WasmGCDeclarationsGenerator declarationsGenerator) { + var nativeExceptionField = declarationsGenerator.classInfoProvider().getThrowableNativeOffset(); + if (nativeExceptionField < 0) { + return; + } + + var throwableType = declarationsGenerator.classInfoProvider().getClassInfo("java.lang.Throwable") + .getStructure(); + + var getFunction = new WasmFunction(declarationsGenerator.functionTypes.of( + WasmType.Reference.EXTERN, throwableType.getReference() + )); + getFunction.setName("teavm.getJsException"); + getFunction.setExportName("teavm.getJsException"); + var getParam = new WasmLocal(throwableType.getReference(), "javaException"); + getFunction.add(getParam); + var getField = new WasmStructGet(throwableType, new WasmGetLocal(getParam), nativeExceptionField); + getFunction.getBody().add(getField); + declarationsGenerator.module.functions.add(getFunction); + + var setFunction = new WasmFunction(declarationsGenerator.functionTypes.of(null, throwableType.getReference(), + WasmType.Reference.EXTERN)); + setFunction.setName("teavm.setJsException"); + setFunction.setExportName("teavm.setJsException"); + var setParam = new WasmLocal(throwableType.getReference(), "javaException"); + var setValue = new WasmLocal(WasmType.Reference.EXTERN, "jsException"); + setFunction.add(setParam); + setFunction.add(setValue); + var setField = new WasmStructSet(throwableType, new WasmGetLocal(setParam), + nativeExceptionField, new WasmGetLocal(setValue)); + setFunction.getBody().add(setField); + declarationsGenerator.module.functions.add(setFunction); + } + private WasmGCClassConsumerContext createClassConsumerContext( ClassReaderSource classes, WasmGCDeclarationsGenerator generator diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index 9fcd0a6dc..edb6fd287 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -147,6 +147,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private int arrayCopyOffset = -1; private int cloneOffset = -1; private int servicesOffset = -1; + private int throwableNativeOffset = -1; private WasmStructure arrayVirtualTableStruct; private WasmFunction arrayGetObjectFunction; private WasmFunction arrayLengthObjectFunction; @@ -482,6 +483,11 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit return servicesOffset; } + @Override + public int getThrowableNativeOffset() { + return throwableNativeOffset; + } + private void initPrimitiveClass(WasmGCClassInfo classInfo, ValueType.Primitive type) { classInfo.initializer = target -> { int kind; @@ -1221,6 +1227,11 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit var field = new WasmField(WasmType.Reference.EXTERN.asStorage(), "nativeRef"); fields.add(field); } + if (className.equals("java.lang.Throwable")) { + throwableNativeOffset = fields.size(); + var field = new WasmField(WasmType.Reference.EXTERN.asStorage(), "nativeRef"); + fields.add(field); + } if (className.equals("java.lang.Class")) { var cls = classSource.get("java.lang.Class"); classFlagsOffset = fields.size(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java index f43243a01..04492153d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java @@ -73,6 +73,8 @@ public interface WasmGCClassInfoProvider { int getServicesOffset(); + int getThrowableNativeOffset(); + default WasmGCClassInfo getClassInfo(String name) { return getClassInfo(ValueType.object(name)); } diff --git a/core/src/main/js/wasm-gc-runtime/runtime.js b/core/src/main/js/wasm-gc-runtime/runtime.js index 398a0c95c..013beac3d 100644 --- a/core/src/main/js/wasm-gc-runtime/runtime.js +++ b/core/src/main/js/wasm-gc-runtime/runtime.js @@ -58,6 +58,7 @@ class JavaError extends Error { super(); this.#context = context; this[javaExceptionSymbol] = javaException; + context.exports["teavm.setJsException"](javaException, this); } get message() { let exceptionMessage = this.#context.exports["teavm.exceptionMessage"]; @@ -180,6 +181,9 @@ function coreImports(imports, context) { return result; } }; + }, + decorateException(javaException) { + new JavaError(context, javaException); } }; } @@ -195,7 +199,6 @@ function jsoImports(imports, context) { let primitiveWrappers = new Map(); let primitiveFinalization = new FinalizationRegistry(token => primitiveWrappers.delete(token)); let hashCodes = new WeakMap(); - let javaExceptionWrappers = new WeakMap(); let lastHashCode = 2463534242; let nextHashCode = () => { let x = lastHashCode; @@ -235,21 +238,17 @@ function jsoImports(imports, context) { function javaExceptionToJs(e) { if (e instanceof WebAssembly.Exception) { let tag = context.exports["teavm.javaException"]; + let getJsException = context.exports["teavm.getJsException"]; if (e.is(tag)) { let javaException = e.getArg(tag, 0); let extracted = extractException(javaException); if (extracted !== null) { return extracted; } - let wrapperRef = javaExceptionWrappers.get(javaException); - if (typeof wrapperRef != "undefined") { - let wrapper = wrapperRef.deref(); - if (typeof wrapper !== "undefined") { - return wrapper; - } + let wrapper = getJsException(javaException); + if (typeof wrapper === "undefined") { + wrapper = new JavaError(context, javaException); } - let wrapper = new JavaError(context, javaException); - javaExceptionWrappers.set(javaException, new WeakRef(wrapper)); return wrapper; } }