mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
Add API to catch native JS exceptions
This commit is contained in:
parent
cf9090e0fa
commit
a3dfc0c486
|
@ -611,6 +611,14 @@ function $rt_intBitsToFloat(n) {
|
||||||
return $rt_numberConversionView.getFloat32(0);
|
return $rt_numberConversionView.getFloat32(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function $rt_javaException(e) {
|
||||||
|
return e instanceof Error && typeof e.$javaException === 'object' ? e.$javaException : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function $rt_jsException(e) {
|
||||||
|
return typeof e.$jsException === 'object' ? e.$jsException : null;
|
||||||
|
}
|
||||||
|
|
||||||
function $dbg_class(obj) {
|
function $dbg_class(obj) {
|
||||||
var cls = obj.constructor;
|
var cls = obj.constructor;
|
||||||
var arrayDegree = 0;
|
var arrayDegree = 0;
|
||||||
|
|
53
jso/apis/src/main/java/org/teavm/jso/core/JSError.java
Normal file
53
jso/apis/src/main/java/org/teavm/jso/core/JSError.java
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.core;
|
||||||
|
|
||||||
|
import org.teavm.jso.JSBody;
|
||||||
|
import org.teavm.jso.JSFunctor;
|
||||||
|
import org.teavm.jso.JSObject;
|
||||||
|
import org.teavm.jso.JSProperty;
|
||||||
|
|
||||||
|
public abstract class JSError implements JSObject {
|
||||||
|
@JSBody(params = { "tryClause", "catchClause" }, script = ""
|
||||||
|
+ "try {"
|
||||||
|
+ "return tryClause();"
|
||||||
|
+ "} catch (e) {"
|
||||||
|
+ "return catchClause(e);"
|
||||||
|
+ "}")
|
||||||
|
public static native <T extends JSObject> T catchNative(TryClause<T> tryClause, CatchClause<T> catchClause);
|
||||||
|
|
||||||
|
@JSBody(params = "object", script = "return object instanceof Error;")
|
||||||
|
public static native boolean isError(JSObject object);
|
||||||
|
|
||||||
|
@JSProperty
|
||||||
|
public abstract String getStack();
|
||||||
|
|
||||||
|
@JSProperty
|
||||||
|
public abstract String getMessage();
|
||||||
|
|
||||||
|
@JSProperty
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
@JSFunctor
|
||||||
|
public interface TryClause<T extends JSObject> extends JSObject {
|
||||||
|
T run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JSFunctor
|
||||||
|
public interface CatchClause<T extends JSObject> extends JSObject {
|
||||||
|
T accept(JSObject e);
|
||||||
|
}
|
||||||
|
}
|
25
jso/core/src/main/java/org/teavm/jso/JSExceptions.java
Normal file
25
jso/core/src/main/java/org/teavm/jso/JSExceptions.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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;
|
||||||
|
|
||||||
|
public final class JSExceptions {
|
||||||
|
private JSExceptions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static native Throwable getJavaException(JSObject e);
|
||||||
|
|
||||||
|
public static native JSObject getJSException(Throwable e);
|
||||||
|
}
|
|
@ -23,7 +23,6 @@ import org.mozilla.javascript.ast.ExpressionStatement;
|
||||||
import org.mozilla.javascript.ast.Name;
|
import org.mozilla.javascript.ast.Name;
|
||||||
import org.mozilla.javascript.ast.NodeVisitor;
|
import org.mozilla.javascript.ast.NodeVisitor;
|
||||||
import org.mozilla.javascript.ast.ReturnStatement;
|
import org.mozilla.javascript.ast.ReturnStatement;
|
||||||
import org.mozilla.javascript.ast.ThrowStatement;
|
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
import org.teavm.model.ValueType;
|
import org.teavm.model.ValueType;
|
||||||
|
|
||||||
|
@ -63,8 +62,6 @@ final class JSBodyInlineUtil {
|
||||||
if (method.getReturnType() == ValueType.VOID) {
|
if (method.getReturnType() == ValueType.VOID) {
|
||||||
if (statement instanceof ExpressionStatement) {
|
if (statement instanceof ExpressionStatement) {
|
||||||
return ((ExpressionStatement) statement).getExpression();
|
return ((ExpressionStatement) statement).getExpression();
|
||||||
} else if (statement instanceof ThrowStatement) {
|
|
||||||
return ((ThrowStatement) statement).getExpression();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (statement instanceof ReturnStatement) {
|
if (statement instanceof ReturnStatement) {
|
||||||
|
|
|
@ -28,5 +28,4 @@ class JSBodyRepository {
|
||||||
public final Set<MethodReference> inlineMethods = new HashSet<>();
|
public final Set<MethodReference> inlineMethods = new HashSet<>();
|
||||||
public final Map<MethodReference, MethodReference> callbackCallees = new HashMap<>();
|
public final Map<MethodReference, MethodReference> callbackCallees = new HashMap<>();
|
||||||
public final Map<MethodReference, Set<MethodReference>> callbackMethods = new HashMap<>();
|
public final Map<MethodReference, Set<MethodReference>> callbackMethods = new HashMap<>();
|
||||||
public final Map<MethodReference, Set<MethodReference>> callbackMethodsDeps = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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;
|
||||||
|
|
||||||
|
import org.teavm.dependency.AbstractDependencyListener;
|
||||||
|
import org.teavm.dependency.DependencyAgent;
|
||||||
|
import org.teavm.dependency.DependencyNode;
|
||||||
|
import org.teavm.dependency.MethodDependency;
|
||||||
|
import org.teavm.jso.JSExceptions;
|
||||||
|
import org.teavm.model.CallLocation;
|
||||||
|
|
||||||
|
public class JSExceptionsDependencyListener extends AbstractDependencyListener {
|
||||||
|
private DependencyNode allExceptions;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started(DependencyAgent agent) {
|
||||||
|
allExceptions = agent.createNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
|
||||||
|
if (method.getReference().getClassName().equals(JSExceptions.class.getName())) {
|
||||||
|
if (method.getReference().getName().equals("getJavaException")) {
|
||||||
|
allExceptions.connect(method.getResult());
|
||||||
|
}
|
||||||
|
} else if (method.getReference().getClassName().equals(JS.class.getName())) {
|
||||||
|
switch (method.getReference().getName()) {
|
||||||
|
case "get":
|
||||||
|
case "set":
|
||||||
|
case "invoke":
|
||||||
|
allExceptions.connect(method.getThrown());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
method.getThrown().connect(allExceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.teavm.backend.javascript.spi.Injector;
|
||||||
|
import org.teavm.backend.javascript.spi.InjectorContext;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
|
public class JSExceptionsGenerator implements Injector {
|
||||||
|
@Override
|
||||||
|
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||||
|
switch (methodRef.getName()) {
|
||||||
|
case "getJavaException":
|
||||||
|
context.getWriter().append("$rt_javaException(");
|
||||||
|
context.writeExpr(context.getArgument(0));
|
||||||
|
context.getWriter().append(")");
|
||||||
|
break;
|
||||||
|
case "getJSException":
|
||||||
|
context.getWriter().append("$rt_jsException(");
|
||||||
|
context.writeExpr(context.getArgument(0));
|
||||||
|
context.getWriter().append(")");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,9 @@
|
||||||
package org.teavm.jso.impl;
|
package org.teavm.jso.impl;
|
||||||
|
|
||||||
import org.teavm.backend.javascript.TeaVMJavaScriptHost;
|
import org.teavm.backend.javascript.TeaVMJavaScriptHost;
|
||||||
|
import org.teavm.jso.JSExceptions;
|
||||||
|
import org.teavm.jso.JSObject;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
import org.teavm.vm.TeaVMPluginUtil;
|
import org.teavm.vm.TeaVMPluginUtil;
|
||||||
import org.teavm.vm.spi.TeaVMHost;
|
import org.teavm.vm.spi.TeaVMHost;
|
||||||
import org.teavm.vm.spi.TeaVMPlugin;
|
import org.teavm.vm.spi.TeaVMPlugin;
|
||||||
|
@ -34,6 +37,7 @@ public class JSOPlugin implements TeaVMPlugin {
|
||||||
JSDependencyListener dependencyListener = new JSDependencyListener(repository);
|
JSDependencyListener dependencyListener = new JSDependencyListener(repository);
|
||||||
JSAliasRenderer aliasRenderer = new JSAliasRenderer();
|
JSAliasRenderer aliasRenderer = new JSAliasRenderer();
|
||||||
host.add(dependencyListener);
|
host.add(dependencyListener);
|
||||||
|
host.add(new JSExceptionsDependencyListener());
|
||||||
|
|
||||||
jsHost.add(aliasRenderer);
|
jsHost.add(aliasRenderer);
|
||||||
jsHost.addGeneratorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
|
jsHost.addGeneratorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
|
||||||
|
@ -42,6 +46,12 @@ public class JSOPlugin implements TeaVMPlugin {
|
||||||
DynamicInjector.class.getName()));
|
DynamicInjector.class.getName()));
|
||||||
jsHost.addVirtualMethods(aliasRenderer);
|
jsHost.addVirtualMethods(aliasRenderer);
|
||||||
|
|
||||||
|
JSExceptionsGenerator exceptionsGenerator = new JSExceptionsGenerator();
|
||||||
|
jsHost.add(new MethodReference(JSExceptions.class, "getJavaException", JSObject.class, Throwable.class),
|
||||||
|
exceptionsGenerator);
|
||||||
|
jsHost.add(new MethodReference(JSExceptions.class, "getJSException", Throwable.class, JSObject.class),
|
||||||
|
exceptionsGenerator);
|
||||||
|
|
||||||
TeaVMPluginUtil.handleNatives(host, JS.class);
|
TeaVMPluginUtil.handleNatives(host, JS.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
123
tests/src/test/java/org/teavm/jso/test/ExceptionsTest.java
Normal file
123
tests/src/test/java/org/teavm/jso/test/ExceptionsTest.java
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.teavm.jso.JSBody;
|
||||||
|
import org.teavm.jso.JSExceptions;
|
||||||
|
import org.teavm.jso.JSFunctor;
|
||||||
|
import org.teavm.jso.JSObject;
|
||||||
|
import org.teavm.jso.core.JSError;
|
||||||
|
import org.teavm.junit.SkipJVM;
|
||||||
|
import org.teavm.junit.TeaVMTestRunner;
|
||||||
|
|
||||||
|
@RunWith(TeaVMTestRunner.class)
|
||||||
|
@SkipJVM
|
||||||
|
public class ExceptionsTest {
|
||||||
|
@Test
|
||||||
|
public void throwExceptionThroughJSCode() {
|
||||||
|
JSRunnable[] actions = new JSRunnable[] {
|
||||||
|
() -> {
|
||||||
|
throw new CustomException1();
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
throw new CustomException2();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (JSRunnable action : actions) {
|
||||||
|
try {
|
||||||
|
runJsCode(action);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
sb.append(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("foobar", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void catchNativeException() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
JSError.catchNative(() -> {
|
||||||
|
throwNativeException();
|
||||||
|
return null;
|
||||||
|
}, e -> {
|
||||||
|
sb.append("caught");
|
||||||
|
assertTrue("Should catch Error", JSError.isError(e));
|
||||||
|
|
||||||
|
JSError error = (JSError) e;
|
||||||
|
assertEquals("foo", error.getMessage());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
assertEquals("caught", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void catchThrowableAsNativeException() {
|
||||||
|
JSRunnable[] actions = new JSRunnable[] {
|
||||||
|
() -> {
|
||||||
|
throw new CustomException1();
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
throw new CustomException2();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (JSRunnable action : actions) {
|
||||||
|
JSError.catchNative(() -> {
|
||||||
|
runJsCode(action);
|
||||||
|
return null;
|
||||||
|
}, e -> {
|
||||||
|
Throwable t = JSExceptions.getJavaException(e);
|
||||||
|
sb.append(t.getMessage());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("foobar", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@JSBody(params = "runnable", script = "runnable();")
|
||||||
|
private static native void runJsCode(JSRunnable runnable);
|
||||||
|
|
||||||
|
@JSBody(script = "throw new Error('foo');")
|
||||||
|
private static native void throwNativeException();
|
||||||
|
|
||||||
|
@JSFunctor
|
||||||
|
interface JSRunnable extends JSObject {
|
||||||
|
void run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomException1 extends RuntimeException {
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "foo";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomException2 extends RuntimeException {
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "bar";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user