wasm gc: improve exception stack trace when exception is thrown from wasm and rethrown between Java and JS

This commit is contained in:
Alexey Andreev 2024-10-24 20:05:57 +02:00
parent 24d672e820
commit 2e0864017b
5 changed files with 65 additions and 9 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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();

View File

@ -73,6 +73,8 @@ public interface WasmGCClassInfoProvider {
int getServicesOffset();
int getThrowableNativeOffset();
default WasmGCClassInfo getClassInfo(String name) {
return getClassInfo(ValueType.object(name));
}

View File

@ -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;
}
}