mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-21 16:04:09 -08:00
wasm gc: support JS exceptions
This commit is contained in:
parent
1d47146f43
commit
3218a00eb9
|
@ -110,16 +110,17 @@ TeaVM.wasm = function() {
|
|||
}
|
||||
|
||||
function jsoImports(imports) {
|
||||
|
||||
let javaObjectSymbol = Symbol("javaObject");
|
||||
let functionsSymbol = Symbol("functions");
|
||||
let functionOriginSymbol = Symbol("functionOrigin");
|
||||
let javaExceptionSymbol = Symbol("javaException");
|
||||
|
||||
let jsWrappers = new WeakMap();
|
||||
let javaWrappers = new WeakMap();
|
||||
let primitiveWrappers = new Map();
|
||||
let primitiveFinalization = new FinalizationRegistry(token => primitiveFinalization.delete(token));
|
||||
let hashCodes = new WeakMap();
|
||||
let javaExceptionWrappers = new WeakMap();
|
||||
let lastHashCode = 2463534242;
|
||||
let nextHashCode = () => {
|
||||
let x = lastHashCode;
|
||||
|
@ -156,6 +157,53 @@ TeaVM.wasm = function() {
|
|||
obj[prop] = value;
|
||||
}
|
||||
}
|
||||
function javaExceptionToJs(e) {
|
||||
if (e instanceof WebAssembly.Exception) {
|
||||
let tag = exports["javaException"];
|
||||
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 = new Error();
|
||||
javaExceptionWrappers.set(javaException, new WeakRef(wrapper));
|
||||
wrapper[javaExceptionSymbol] = javaException;
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
function jsExceptionAsJava(e) {
|
||||
if (javaExceptionSymbol in e) {
|
||||
return e[javaExceptionSymbol];
|
||||
} else {
|
||||
return exports["teavm.js.wrapException"](e);
|
||||
}
|
||||
}
|
||||
function rethrowJsAsJava(e) {
|
||||
exports["teavm.js.throwException"](jsExceptionAsJava(e));
|
||||
}
|
||||
function extractException(e) {
|
||||
return exports["teavm.js.extractException"](e);
|
||||
}
|
||||
function rethrowJavaAsJs(e) {
|
||||
throw javaExceptionToJs(e);
|
||||
}
|
||||
function getProperty(obj, prop) {
|
||||
try {
|
||||
return obj !== null ? obj[prop] : getGlobalName(prop)
|
||||
} catch (e) {
|
||||
rethrowJsAsJava(e);
|
||||
}
|
||||
}
|
||||
imports.teavmJso = {
|
||||
emptyString: () => "",
|
||||
stringFromCharCode: code => String.fromCharCode(code),
|
||||
|
@ -166,11 +214,17 @@ TeaVM.wasm = function() {
|
|||
appendToArray: (array, e) => array.push(e),
|
||||
unwrapBoolean: value => value ? 1 : 0,
|
||||
wrapBoolean: value => !!value,
|
||||
getProperty: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop),
|
||||
getPropertyPure: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop),
|
||||
getProperty: getProperty,
|
||||
getPropertyPure: getProperty,
|
||||
setProperty: setProperty,
|
||||
setPropertyPure: setProperty,
|
||||
global: getGlobalName,
|
||||
global(name) {
|
||||
try {
|
||||
return getGlobalName(name);
|
||||
} catch (e) {
|
||||
rethrowJsAsJava(e);
|
||||
}
|
||||
},
|
||||
createClass(name) {
|
||||
let fn = new Function(
|
||||
"javaObjectSymbol",
|
||||
|
@ -184,18 +238,30 @@ TeaVM.wasm = function() {
|
|||
},
|
||||
defineMethod(cls, name, fn) {
|
||||
cls.prototype[name] = function(...args) {
|
||||
return fn(this, ...args);
|
||||
try {
|
||||
return fn(this, ...args);
|
||||
} catch (e) {
|
||||
rethrowJavaAsJs(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
defineProperty(cls, name, getFn, setFn) {
|
||||
let descriptor = {
|
||||
get() {
|
||||
return getFn(this);
|
||||
try {
|
||||
return getFn(this);
|
||||
} catch (e) {
|
||||
rethrowJavaAsJs(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (setFn !== null) {
|
||||
descriptor.set = function(value) {
|
||||
setFn(this, value);
|
||||
try {
|
||||
setFn(this, value);
|
||||
} catch (e) {
|
||||
rethrowJavaAsJs(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.defineProperty(cls.prototype, name, descriptor);
|
||||
|
@ -239,7 +305,15 @@ TeaVM.wasm = function() {
|
|||
return origin;
|
||||
}
|
||||
}
|
||||
return { [property]: fn };
|
||||
return {
|
||||
[property]: function(...args) {
|
||||
try {
|
||||
return fn(...args);
|
||||
} catch (e) {
|
||||
rethrowJavaAsJs(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
wrapObject(obj) {
|
||||
if (obj === null) {
|
||||
|
@ -298,25 +372,47 @@ TeaVM.wasm = function() {
|
|||
}
|
||||
},
|
||||
apply: (instance, method, args) => {
|
||||
if (instance === null) {
|
||||
let fn = getGlobalName(method);
|
||||
return fn(...args);
|
||||
} else {
|
||||
return instance[method](...args);
|
||||
try {
|
||||
if (instance === null) {
|
||||
let fn = getGlobalName(method);
|
||||
return fn(...args);
|
||||
} else {
|
||||
return instance[method](...args);
|
||||
}
|
||||
} catch (e) {
|
||||
rethrowJsAsJava(e);
|
||||
}
|
||||
},
|
||||
concatArray: (a, b) => a.concat(b)
|
||||
concatArray: (a, b) => a.concat(b),
|
||||
getJavaException: e => e[javaExceptionSymbol]
|
||||
};
|
||||
for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte",
|
||||
"unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) {
|
||||
"unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) {
|
||||
imports.teavmJso[name] = identity;
|
||||
}
|
||||
for (let i = 0; i < 32; ++i) {
|
||||
imports.teavmJso["createFunction" + i] = (...args) => new Function(...args);
|
||||
imports.teavmJso["callFunction" + i] = (fn, ...args) => fn(...args);
|
||||
imports.teavmJso["callMethod" + i] = (instance, method, ...args) =>
|
||||
instance !== null ? instance[method](...args) : getGlobalName(method)(...args);
|
||||
imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args);
|
||||
imports.teavmJso["callFunction" + i] = (fn, ...args) => {
|
||||
try {
|
||||
return fn(...args);
|
||||
} catch (e) {
|
||||
rethrowJsAsJava(e);
|
||||
}
|
||||
};
|
||||
imports.teavmJso["callMethod" + i] = (instance, method, ...args) => {
|
||||
try {
|
||||
return instance !== null ? instance[method](...args) : getGlobalName(method)(...args);
|
||||
} catch (e) {
|
||||
rethrowJsAsJava(e);
|
||||
}
|
||||
}
|
||||
imports.teavmJso["construct" + i] = (constructor, ...args) => {
|
||||
try {
|
||||
return new constructor(...args);
|
||||
} catch (e) {
|
||||
rethrowJsAsJava(e);
|
||||
}
|
||||
}
|
||||
imports.teavmJso["arrayOf" + i] = (...args) => args
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ configurations {
|
|||
|
||||
dependencies {
|
||||
"teavm"(project(":jso:impl"))
|
||||
compileOnly(project(":interop:core"))
|
||||
}
|
||||
|
||||
teavmPublish {
|
||||
|
|
|
@ -15,10 +15,13 @@
|
|||
*/
|
||||
package org.teavm.jso;
|
||||
|
||||
import org.teavm.interop.Import;
|
||||
|
||||
public final class JSExceptions {
|
||||
private JSExceptions() {
|
||||
}
|
||||
|
||||
@Import(name = "getJavaException", module = "teavmJso")
|
||||
public static native Throwable getJavaException(JSObject e);
|
||||
|
||||
public static native JSObject getJSException(Throwable e);
|
||||
|
|
|
@ -68,7 +68,7 @@ class JSValueMarshaller {
|
|||
String className = ((ValueType.Object) type).getClassName();
|
||||
ClassReader cls = classSource.get(className);
|
||||
if (cls != null && cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
|
||||
return wrapFunctor(location, var, cls);
|
||||
return wrapFunctor(location, var, cls, jsType);
|
||||
}
|
||||
}
|
||||
return wrap(var, type, jsType, location.getSourceLocation(), byRef);
|
||||
|
@ -83,20 +83,22 @@ class JSValueMarshaller {
|
|||
.count() == 1;
|
||||
}
|
||||
|
||||
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
|
||||
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type, JSType jsType) {
|
||||
if (!isProperFunctor(type)) {
|
||||
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
|
||||
return var;
|
||||
}
|
||||
|
||||
var unwrapNative = new InvokeInstruction();
|
||||
unwrapNative.setLocation(location.getSourceLocation());
|
||||
unwrapNative.setType(InvocationType.SPECIAL);
|
||||
unwrapNative.setMethod(JSMethods.UNWRAP);
|
||||
unwrapNative.setArguments(var);
|
||||
unwrapNative.setReceiver(program.createVariable());
|
||||
replacement.add(unwrapNative);
|
||||
var = unwrapNative.getReceiver();
|
||||
if (jsType == JSType.JAVA) {
|
||||
var unwrapNative = new InvokeInstruction();
|
||||
unwrapNative.setLocation(location.getSourceLocation());
|
||||
unwrapNative.setType(InvocationType.SPECIAL);
|
||||
unwrapNative.setMethod(JSMethods.UNWRAP);
|
||||
unwrapNative.setArguments(var);
|
||||
unwrapNative.setReceiver(program.createVariable());
|
||||
replacement.add(unwrapNative);
|
||||
var = unwrapNative.getReceiver();
|
||||
}
|
||||
|
||||
String name = type.getMethods().stream()
|
||||
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.jso.impl.wasmgc;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.core.JSError;
|
||||
|
||||
class WasmGCExceptionWrapper extends RuntimeException {
|
||||
final JSObject jsException;
|
||||
|
||||
WasmGCExceptionWrapper(JSObject jsException) {
|
||||
this.jsException = jsException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
var message = jsException instanceof JSError
|
||||
? ((JSError) jsException).getMessage()
|
||||
: jsException.toString();
|
||||
return "(JavaScript) Error: " + message;
|
||||
}
|
||||
}
|
|
@ -28,16 +28,7 @@ import org.teavm.model.MethodReference;
|
|||
class WasmGCJSDependencies extends AbstractDependencyListener {
|
||||
@Override
|
||||
public void started(DependencyAgent agent) {
|
||||
agent.linkMethod(STRING_TO_JS)
|
||||
.propagate(1, agent.getType("java.lang.String"))
|
||||
.use();
|
||||
|
||||
var jsToString = agent.linkMethod(JS_TO_STRING);
|
||||
jsToString.getResult().propagate(agent.getType("java.lang.String"));
|
||||
jsToString.use();
|
||||
|
||||
agent.linkMethod(new MethodReference(JSWrapper.class, "createWrapper", JSObject.class, Object.class))
|
||||
.use();
|
||||
reachUtilities(agent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,6 +37,26 @@ class WasmGCJSDependencies extends AbstractDependencyListener {
|
|||
if (method.getMethod().getName().equals("jsArrayItem")) {
|
||||
method.getVariable(1).getArrayItem().connect(method.getResult());
|
||||
}
|
||||
} else if (method.getMethod().getOwnerName().equals(JSWrapper.class.getName())) {
|
||||
if (method.getMethod().getName().equals("wrap")) {
|
||||
agent.linkMethod(new MethodReference(JSWrapper.class, "createWrapper", JSObject.class, Object.class))
|
||||
.use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reachUtilities(DependencyAgent agent) {
|
||||
agent.linkMethod(STRING_TO_JS)
|
||||
.propagate(1, agent.getType("java.lang.String"))
|
||||
.use();
|
||||
|
||||
var jsToString = agent.linkMethod(JS_TO_STRING);
|
||||
jsToString.getResult().propagate(agent.getType("java.lang.String"));
|
||||
jsToString.use();
|
||||
|
||||
agent.linkMethod(new MethodReference(WasmGCJSRuntime.class, "wrapException", JSObject.class, Throwable.class))
|
||||
.use();
|
||||
agent.linkMethod(new MethodReference(WasmGCJSRuntime.class, "extractException", Throwable.class,
|
||||
JSObject.class)).use();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,4 +65,12 @@ final class WasmGCJSRuntime {
|
|||
|
||||
@Import(name = "charAt", module = "teavmJso")
|
||||
static native char charAt(JSObject str, int index);
|
||||
|
||||
static Throwable wrapException(JSObject obj) {
|
||||
return new WasmGCExceptionWrapper(obj);
|
||||
}
|
||||
|
||||
static JSObject extractException(Throwable e) {
|
||||
return e instanceof WasmGCExceptionWrapper ? ((WasmGCExceptionWrapper) e).jsException : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,20 +22,30 @@ import java.util.function.Consumer;
|
|||
import org.teavm.backend.javascript.rendering.AstWriter;
|
||||
import org.teavm.backend.wasm.model.WasmFunction;
|
||||
import org.teavm.backend.wasm.model.WasmGlobal;
|
||||
import org.teavm.backend.wasm.model.WasmLocal;
|
||||
import org.teavm.backend.wasm.model.WasmType;
|
||||
import org.teavm.backend.wasm.model.expression.WasmCall;
|
||||
import org.teavm.backend.wasm.model.expression.WasmCast;
|
||||
import org.teavm.backend.wasm.model.expression.WasmExpression;
|
||||
import org.teavm.backend.wasm.model.expression.WasmExternConversion;
|
||||
import org.teavm.backend.wasm.model.expression.WasmExternConversionType;
|
||||
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
|
||||
import org.teavm.backend.wasm.model.expression.WasmGetLocal;
|
||||
import org.teavm.backend.wasm.model.expression.WasmNullConstant;
|
||||
import org.teavm.backend.wasm.model.expression.WasmSetGlobal;
|
||||
import org.teavm.backend.wasm.model.expression.WasmThrow;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.impl.JSBodyAstEmitter;
|
||||
import org.teavm.jso.impl.JSBodyBloatedEmitter;
|
||||
import org.teavm.jso.impl.JSBodyEmitter;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.ValueType;
|
||||
|
||||
class WasmGCJsoCommonGenerator {
|
||||
private WasmGCJSFunctions jsFunctions;
|
||||
private boolean initialized;
|
||||
private List<Consumer<WasmFunction>> initializerParts = new ArrayList<>();
|
||||
private boolean rethrowExported;
|
||||
|
||||
WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) {
|
||||
this.jsFunctions = jsFunctions;
|
||||
|
@ -47,6 +57,7 @@ class WasmGCJsoCommonGenerator {
|
|||
}
|
||||
initialized = true;
|
||||
context.addToInitializer(this::writeToInitializer);
|
||||
exportRethrowException(context);
|
||||
}
|
||||
|
||||
private void writeToInitializer(WasmFunction function) {
|
||||
|
@ -55,7 +66,8 @@ class WasmGCJsoCommonGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
void addInitializerPart(Consumer<WasmFunction> part) {
|
||||
void addInitializerPart(WasmGCJsoContext context, Consumer<WasmFunction> part) {
|
||||
initialize(context);
|
||||
initializerParts.add(part);
|
||||
}
|
||||
|
||||
|
@ -114,4 +126,37 @@ class WasmGCJsoCommonGenerator {
|
|||
WasmExpression stringToJs(WasmGCJsoContext context, WasmExpression str) {
|
||||
return new WasmCall(stringToJsFunction(context), str);
|
||||
}
|
||||
|
||||
private void exportRethrowException(WasmGCJsoContext context) {
|
||||
if (rethrowExported) {
|
||||
return;
|
||||
}
|
||||
rethrowExported = true;
|
||||
var fn = context.functions().forStaticMethod(new MethodReference(WasmGCJSRuntime.class, "wrapException",
|
||||
JSObject.class, Throwable.class));
|
||||
fn.setExportName("teavm.js.wrapException");
|
||||
|
||||
fn = context.functions().forStaticMethod(new MethodReference(WasmGCJSRuntime.class, "extractException",
|
||||
Throwable.class, JSObject.class));
|
||||
fn.setExportName("teavm.js.extractException");
|
||||
|
||||
createThrowExceptionFunction(context);
|
||||
}
|
||||
|
||||
private void createThrowExceptionFunction(WasmGCJsoContext context) {
|
||||
var fn = new WasmFunction(context.functionTypes().of(null, WasmType.Reference.EXTERN));
|
||||
fn.setName(context.names().topLevel("teavm@throwException"));
|
||||
fn.setExportName("teavm.js.throwException");
|
||||
context.module().functions.add(fn);
|
||||
|
||||
var exceptionLocal = new WasmLocal(WasmType.Reference.EXTERN);
|
||||
fn.add(exceptionLocal);
|
||||
|
||||
var asAny = new WasmExternConversion(WasmExternConversionType.EXTERN_TO_ANY, new WasmGetLocal(exceptionLocal));
|
||||
var throwableType = (WasmType.Reference) context.typeMapper().mapType(ValueType.parse(Throwable.class));
|
||||
var asThrowable = new WasmCast(asAny, throwableType);
|
||||
var throwExpr = new WasmThrow(context.exceptionTag());
|
||||
throwExpr.getArguments().add(asThrowable);
|
||||
fn.getBody().add(throwExpr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,13 @@ import java.util.function.Consumer;
|
|||
import org.teavm.backend.wasm.BaseWasmFunctionRepository;
|
||||
import org.teavm.backend.wasm.WasmFunctionTypes;
|
||||
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
|
||||
import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper;
|
||||
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider;
|
||||
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorContext;
|
||||
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext;
|
||||
import org.teavm.backend.wasm.model.WasmFunction;
|
||||
import org.teavm.backend.wasm.model.WasmModule;
|
||||
import org.teavm.backend.wasm.model.WasmTag;
|
||||
|
||||
interface WasmGCJsoContext {
|
||||
WasmModule module();
|
||||
|
@ -36,6 +38,10 @@ interface WasmGCJsoContext {
|
|||
|
||||
WasmGCStringProvider strings();
|
||||
|
||||
WasmGCTypeMapper typeMapper();
|
||||
|
||||
WasmTag exceptionTag();
|
||||
|
||||
void addToInitializer(Consumer<WasmFunction> initializerContributor);
|
||||
|
||||
static WasmGCJsoContext wrap(WasmGCIntrinsicContext context) {
|
||||
|
@ -65,6 +71,16 @@ interface WasmGCJsoContext {
|
|||
return context.strings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WasmGCTypeMapper typeMapper() {
|
||||
return context.typeMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WasmTag exceptionTag() {
|
||||
return context.exceptionTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
|
||||
context.addToInitializer(initializerContributor);
|
||||
|
@ -99,6 +115,16 @@ interface WasmGCJsoContext {
|
|||
return context.strings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WasmGCTypeMapper typeMapper() {
|
||||
return context.typeMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WasmTag exceptionTag() {
|
||||
return context.exceptionTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
|
||||
context.addToInitializer(initializerContributor);
|
||||
|
|
|
@ -27,12 +27,13 @@ import org.teavm.jso.core.JSError;
|
|||
import org.teavm.junit.EachTestCompiledSeparately;
|
||||
import org.teavm.junit.OnlyPlatform;
|
||||
import org.teavm.junit.SkipJVM;
|
||||
import org.teavm.junit.SkipPlatform;
|
||||
import org.teavm.junit.TeaVMTestRunner;
|
||||
import org.teavm.junit.TestPlatform;
|
||||
|
||||
@RunWith(TeaVMTestRunner.class)
|
||||
@SkipJVM
|
||||
@OnlyPlatform(TestPlatform.JAVASCRIPT)
|
||||
@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
|
||||
@EachTestCompiledSeparately
|
||||
public class ExceptionsTest {
|
||||
@Test
|
||||
|
@ -102,6 +103,7 @@ public class ExceptionsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipPlatform(TestPlatform.WEBASSEMBLY_GC)
|
||||
public void catchNativeExceptionAsRuntimeException() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
|
|
Loading…
Reference in New Issue
Block a user