mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -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);
|
||||
}
|
||||
|
||||
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) {
|
||||
var cls = obj.constructor;
|
||||
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.NodeVisitor;
|
||||
import org.mozilla.javascript.ast.ReturnStatement;
|
||||
import org.mozilla.javascript.ast.ThrowStatement;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.ValueType;
|
||||
|
||||
|
@ -63,8 +62,6 @@ final class JSBodyInlineUtil {
|
|||
if (method.getReturnType() == ValueType.VOID) {
|
||||
if (statement instanceof ExpressionStatement) {
|
||||
return ((ExpressionStatement) statement).getExpression();
|
||||
} else if (statement instanceof ThrowStatement) {
|
||||
return ((ThrowStatement) statement).getExpression();
|
||||
}
|
||||
} else {
|
||||
if (statement instanceof ReturnStatement) {
|
||||
|
|
|
@ -28,5 +28,4 @@ class JSBodyRepository {
|
|||
public final Set<MethodReference> inlineMethods = new HashSet<>();
|
||||
public final Map<MethodReference, MethodReference> callbackCallees = 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;
|
||||
|
||||
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.spi.TeaVMHost;
|
||||
import org.teavm.vm.spi.TeaVMPlugin;
|
||||
|
@ -34,6 +37,7 @@ public class JSOPlugin implements TeaVMPlugin {
|
|||
JSDependencyListener dependencyListener = new JSDependencyListener(repository);
|
||||
JSAliasRenderer aliasRenderer = new JSAliasRenderer();
|
||||
host.add(dependencyListener);
|
||||
host.add(new JSExceptionsDependencyListener());
|
||||
|
||||
jsHost.add(aliasRenderer);
|
||||
jsHost.addGeneratorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
|
||||
|
@ -42,6 +46,12 @@ public class JSOPlugin implements TeaVMPlugin {
|
|||
DynamicInjector.class.getName()));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
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