Generate entire code inside wrapper IIF

This commit is contained in:
Alexey Andreev 2018-11-15 12:47:25 +03:00
parent fe151d525a
commit 148c07336c
35 changed files with 520 additions and 532 deletions

View File

@ -446,7 +446,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost {
private void generateCallToMainMethod(GenerationContext context, CodeWriter writer) {
TeaVMEntryPoint entryPoint = controller.getEntryPoints().get("main");
if (entryPoint != null) {
String mainMethod = context.getNames().forMethod(entryPoint.getReference());
String mainMethod = context.getNames().forMethod(entryPoint.getMethod());
writer.println(mainMethod + "(NULL);");
}
}

View File

@ -235,6 +235,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
MethodDependency exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(
NoClassDefFoundError.class, "<init>", String.class, void.class), null);
dep = dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "toString", String.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.Object"));
dep.use();
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoClassDefFoundError.class.getName()));
exceptionCons.getVariable(1).propagate(dependencyAnalyzer.getType("java.lang.String"));
exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchFieldError.class, "<init>",
@ -315,26 +319,34 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
renderingContext.addInjector(entry.getKey(), entry.getValue());
}
try {
printWrapperStart(sourceWriter);
for (RendererListener listener : rendererListeners) {
listener.begin(renderer, target);
}
int start = sourceWriter.getOffset();
sourceWriter.append("\"use strict\";").newLine();
renderer.prepare(clsNodes);
runtimeRenderer.renderRuntime();
renderer.render(clsNodes);
renderer.renderStringPool();
renderer.renderStringConstants();
renderer.renderCompatibilityStubs();
for (Map.Entry<? extends String, ? extends TeaVMEntryPoint> entry
: controller.getEntryPoints().entrySet()) {
sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws();
MethodReference ref = entry.getValue().getReference();
sourceWriter.append(naming.getFullNameFor(ref));
sourceWriter.append(";").newLine();
sourceWriter.append("").append(entry.getKey()).ws().append("=").ws();
MethodReference ref = entry.getValue().getMethod();
sourceWriter.append("$rt_mainStarter(").append(naming.getFullNameFor(ref));
sourceWriter.append(");").newLine();
}
for (RendererListener listener : rendererListeners) {
listener.complete();
}
printWrapperEnd(sourceWriter);
int totalSize = sourceWriter.getOffset() - start;
printStats(renderer, totalSize);
} catch (IOException e) {
@ -342,6 +354,18 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
}
private void printWrapperStart(SourceWriter writer) throws IOException {
writer.append("\"use strict\";").newLine();
for (String key : controller.getEntryPoints().keySet()) {
writer.append("var ").append(key).append(";").softNewLine();
}
writer.append("(function()").ws().append("{").newLine();
}
private void printWrapperEnd(SourceWriter writer) throws IOException {
writer.append("})();").newLine();
}
private void printStats(Renderer renderer, int totalSize) {
if (!Boolean.parseBoolean(System.getProperty("teavm.js.stats", "false"))) {
return;

View File

@ -189,6 +189,40 @@ public class Renderer implements RenderingManager {
}
}
public void renderCompatibilityStubs() throws RenderingException {
try {
renderJavaStringToString();
renderJavaObjectToString();
renderTeaVMClass();
} catch (IOException e) {
throw new RenderingException("IO error", e);
}
}
private void renderJavaStringToString() throws IOException {
writer.appendClass("java.lang.String").append(".prototype.toString").ws().append("=").ws()
.append("function()").ws().append("{").indent().softNewLine();
writer.append("return $rt_ustr(this);").softNewLine();
writer.outdent().append("};").newLine();
writer.appendClass("java.lang.String").append(".prototype.valueOf").ws().append("=").ws()
.appendClass("java.lang.String").append(".prototype.toString;").softNewLine();
}
private void renderJavaObjectToString() throws IOException {
writer.appendClass("java.lang.Object").append(".prototype.toString").ws().append("=").ws()
.append("function()").ws().append("{").indent().softNewLine();
writer.append("return $rt_ustr(").appendMethodBody(Object.class, "toString", String.class).append("(this));")
.softNewLine();
writer.outdent().append("};").newLine();
}
private void renderTeaVMClass() throws IOException {
writer.appendClass("java.lang.Object").append(".prototype.__teavm_class__").ws().append("=").ws()
.append("function()").ws().append("{").indent().softNewLine();
writer.append("return $dbg_class(this);").softNewLine();
writer.outdent().append("};").newLine();
}
private void appendClassSize(String className, int sz) {
sizeByClass.put(className, sizeByClass.getOrDefault(className, 0) + sz);
}

View File

@ -397,7 +397,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
module.setStartFunction(initFunction);
for (TeaVMEntryPoint entryPoint : controller.getEntryPoints().values()) {
String mangledName = names.forMethod(entryPoint.getReference());
String mangledName = names.forMethod(entryPoint.getMethod());
WasmFunction function = module.getFunctions().get(mangledName);
if (function != null) {
function.setExportName(entryPoint.getPublicName());

View File

@ -645,7 +645,6 @@ public class DebugInformation {
int[] methods;
}
class MethodTree {
int[] data;
int[] offsets;

View File

@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@ -37,6 +38,7 @@ import org.teavm.dependency.DependencyInfo;
import org.teavm.dependency.DependencyListener;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.Linker;
import org.teavm.dependency.MethodDependency;
import org.teavm.diagnostics.AccumulationDiagnostics;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.diagnostics.ProblemProvider;
@ -47,12 +49,14 @@ import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.MutableClassHolderSource;
import org.teavm.model.Program;
import org.teavm.model.ProgramCache;
import org.teavm.model.ValueType;
import org.teavm.model.optimization.ArrayUnwrapMotion;
import org.teavm.model.optimization.ClassInitElimination;
import org.teavm.model.optimization.ConstantConditionElimination;
@ -106,11 +110,14 @@ import org.teavm.vm.spi.TeaVMPlugin;
* @author Alexey Andreev
*/
public class TeaVM implements TeaVMHost, ServiceRepository {
private static final MethodDescriptor MAIN_METHOD_DESC = new MethodDescriptor("main",
ValueType.arrayOf(ValueType.object("java.lang.String")), ValueType.VOID);
private final ClassReaderSource classSource;
private final DependencyAnalyzer dependencyAnalyzer;
private final AccumulationDiagnostics diagnostics = new AccumulationDiagnostics();
private final ClassLoader classLoader;
private final Map<String, TeaVMEntryPoint> entryPoints = new HashMap<>();
private final Map<String, TeaVMEntryPoint> entryPoints = new LinkedHashMap<>();
private final Map<String, TeaVMEntryPoint> readonlyEntryPoints = Collections.unmodifiableMap(entryPoints);
private final Set<String> preservedClasses = new HashSet<>();
private final Set<String> readonlyPreservedClasses = Collections.unmodifiableSet(preservedClasses);
@ -243,58 +250,38 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return target.getPlatformTags();
}
/**
* <p>Adds an entry point. TeaVM guarantees, that all methods that are required by the entry point
* will be available at run-time in browser. Also you need to specify for each parameter of entry point
* which actual types will be passed here by calling {@link TeaVMEntryPoint#withValue(int, String)}.
* It is highly recommended to read explanation on {@link TeaVMEntryPoint} class documentation.</p>
*
* <p>You should call this method after installing all plugins and interceptors, but before
* doing the actual build.</p>
*
* @param name the name under which this entry point will be available for JavaScript code.
* @param ref a full reference to the method which is an entry point.
* @return an entry point that you can additionally adjust.
*/
public TeaVMEntryPoint entryPoint(String name, MethodReference ref) {
if (name != null) {
if (entryPoints.containsKey(name)) {
throw new IllegalArgumentException("Entry point with public name `" + name + "' already defined "
+ "for method " + ref);
}
public void entryPoint(String className, String name) {
if (entryPoints.containsKey(name)) {
throw new IllegalArgumentException("Entry point with public name `" + name + "' already defined "
+ "for class " + className);
}
TeaVMEntryPoint entryPoint = new TeaVMEntryPoint(name, ref, dependencyAnalyzer.linkMethod(ref, null));
ClassReader cls = dependencyAnalyzer.getClassSource().get(className);
if (cls == null) {
diagnostics.error(null, "There's no main class: '{{c0}}'", className);
return;
}
if (cls.getMethod(MAIN_METHOD_DESC) == null) {
diagnostics.error(null, "Specified main class '{{c0}}' does not have method '" + MAIN_METHOD_DESC + "'");
return;
}
MethodDependency mainMethod = dependencyAnalyzer.linkMethod(new MethodReference(className,
"main", ValueType.parse(String[].class), ValueType.VOID), null);
TeaVMEntryPoint entryPoint = new TeaVMEntryPoint(name, mainMethod);
dependencyAnalyzer.defer(() -> {
dependencyAnalyzer.linkClass(ref.getClassName(), null).initClass(null);
dependencyAnalyzer.linkClass(className, null).initClass(null);
mainMethod.getVariable(1).propagate(dependencyAnalyzer.getType("[Ljava/lang/String;"));
mainMethod.getVariable(1).getArrayItem().propagate(dependencyAnalyzer.getType("java.lang.String"));
mainMethod.use();
});
if (name != null) {
entryPoints.put(name, entryPoint);
}
return entryPoint;
entryPoints.put(name, entryPoint);
}
/**
* <p>Adds an entry point. TeaVM guarantees, that all methods that are required by the entry point
* will be available at run-time in browser. Also you need to specify for each parameter of entry point
* which actual types will be passed here by calling {@link TeaVMEntryPoint#withValue(int, String)}.
* It is highly recommended to read explanation on {@link TeaVMEntryPoint} class documentation.</p>
*
* <p>You should call this method after installing all plugins and interceptors, but before
* doing the actual build.</p>
*
* @param ref a full reference to the method which is an entry point.
* @return an entry point that you can additionally adjust.
*/
public TeaVMEntryPoint entryPoint(MethodReference ref) {
return entryPoint(null, ref);
}
public TeaVMEntryPoint linkMethod(MethodReference ref) {
TeaVMEntryPoint entryPoint = new TeaVMEntryPoint("", ref, dependencyAnalyzer.linkMethod(ref, null));
dependencyAnalyzer.defer(() -> {
dependencyAnalyzer.linkClass(ref.getClassName(), null).initClass(null);
});
return entryPoint;
public void entryPoint(String className) {
entryPoint(className, "main");
}
public void preserveType(String className) {

View File

@ -15,102 +15,23 @@
*/
package org.teavm.vm;
import java.util.HashMap;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.MethodReference;
/**
* <p>An entry point to a generated VM that is used to enter the VM from a JavaScript code.
* The entry point is added by {@link TeaVM#entryPoint(String, MethodReference)}.
* Use {@link #withValue(int, String)} to specify actual types that are passed to the entry point.</p>
*
* <p>In the simple case of static method without arguments you won't deal with this class. But
* sometimes you have to. Consider the following example:</p>
*
* <pre>{@code
*static void entryPoint(Map<Object, Object> map) {
* for (Map.Entry<Object, Object> entry : map.entrySet()) {
* System.out.println(entry.getKey() + " => " + entry.getValue());
* }
*}}</pre>
*
* <p>Now you want to call this method from JavaScript, and you pass a {@link HashMap} to this method.
* Let's see how you achieve it:</p>
*
* <pre>{@code
*vm.preserveType("JavaHashMap", "java.util.HashMap");
*vm.entryPoint("initJavaHashMap", new MethodReference("java.util.HashMap",
* "<init>", ValueType.VOID));
*vm.entryPoint("putValueIntoJavaMap", new MethodReference(
* "java.util.Map", "put",
* ValueType.object("java.lang.Object"), ValueType.object("java.lang.Object"),
* ValueType.object("java.lang.Object")))
* .withValue(0, "java.util.HashMap")
* .withValue(1, "java.lang.String")
* .withValue(2, "java.lang.String");
*vm.entryPoint("entryPoint", new MethodReference(
* "fully.qualified.ClassName", "entryPoint",
* ValueType.object("java.util.Map"), ValueType.VOID))
* .withValue(1, "java.util.HashMap")
*}</pre>
*
* <p>And in JavaScript you would do the following:</p>
*
* <pre>{@code
*var map = new JavaHashMap();
*initJavaHashMap(map);
*putValueIntoJavaMap(map, $rt_str("foo"), $rt_str("bar"));
*entryPoint(map);
*}</pre>
*
* <p>If you didn't call <code>.withValue(1, "java.util.HashMap")</code>, TeaVM could not know,
* what implementation of <code>entrySet</code> method to include.</p>
*
* @author Alexey Andreev
*/
public class TeaVMEntryPoint {
private String publicName;
MethodReference reference;
private MethodDependency method;
private boolean async;
String publicName;
MethodDependency methodDep;
TeaVMEntryPoint(String publicName, MethodReference reference, MethodDependency method) {
TeaVMEntryPoint(String publicName, MethodDependency methodDep) {
this.publicName = publicName;
this.reference = reference;
this.method = method;
method.use();
}
public MethodReference getReference() {
return reference;
this.methodDep = methodDep;
}
public String getPublicName() {
return publicName;
}
boolean isAsync() {
return async;
}
public TeaVMEntryPoint withValue(int argument, String type) {
if (argument > reference.parameterCount()) {
throw new IllegalArgumentException("Illegal argument #" + argument + " of " + reference.parameterCount());
}
method.getVariable(argument).propagate(method.getDependencyAgent().getType(type));
return this;
}
public TeaVMEntryPoint withArrayValue(int argument, String type) {
if (argument > reference.parameterCount()) {
throw new IllegalArgumentException("Illegal argument #" + argument + " of " + reference.parameterCount());
}
method.getVariable(argument).getArrayItem().propagate(method.getDependencyAgent().getType(type));
return this;
}
public TeaVMEntryPoint async() {
this.async = true;
return this;
public MethodReference getMethod() {
return methodDep.getReference();
}
}

View File

@ -241,11 +241,6 @@ function $rt_voidcls() {
}
return $rt_voidclsCache;
}
function $rt_init(cls, constructor, args) {
var obj = new cls();
cls.prototype[constructor].apply(obj, args);
return obj;
}
function $rt_throw(ex) {
throw $rt_exception(ex);
}
@ -406,7 +401,7 @@ function $rt_assertNotNaN(value) {
return value;
}
var $rt_stdoutBuffer = "";
function $rt_putStdout(ch) {
var $rt_putStdout = typeof $rt_putStdoutCustom === "function" ? $rt_putStdoutCustom : function(ch) {
if (ch === 0xA) {
if (console) {
console.info($rt_stdoutBuffer);
@ -415,9 +410,9 @@ function $rt_putStdout(ch) {
} else {
$rt_stdoutBuffer += String.fromCharCode(ch);
}
}
};
var $rt_stderrBuffer = "";
function $rt_putStderr(ch) {
var $rt_putStderr = typeof $rt_putStderrCustom === "function" ? $rt_putStderrCustom : function(ch) {
if (ch === 0xA) {
if (console) {
console.info($rt_stderrBuffer);
@ -426,7 +421,7 @@ function $rt_putStderr(ch) {
} else {
$rt_stderrBuffer += String.fromCharCode(ch);
}
}
};
function $rt_metadata(data) {
var i = 0;
var packageCount = data[i++];
@ -500,7 +495,7 @@ function $rt_threadStarter(f) {
}
}
function $rt_mainStarter(f) {
return function(args) {
return function(args, callback) {
if (!args) {
args = [];
}
@ -508,8 +503,8 @@ function $rt_mainStarter(f) {
for (var i = 0; i < args.length; ++i) {
javaArgs.data[i] = $rt_str(args[i]);
}
$rt_threadStarter(f)(javaArgs);
};
$rt_startThread(function() { f.call(null, javaArgs); }, callback);
}
}
var $rt_stringPool_instance;
function $rt_stringPool(strings) {
@ -617,14 +612,7 @@ function $rt_nativeThread() {
function $rt_invalidPointer() {
throw new Error("Invalid recorded state");
}
function $dbg_repr(obj) {
return obj.toString ? obj.toString() : "";
}
function $dbg_class(obj) {
if (obj instanceof Long) {
return "long";
}
var cls = obj.constructor;
var arrayDegree = 0;
while (cls.$meta && cls.$meta.item) {
@ -649,7 +637,7 @@ function $dbg_class(obj) {
} else if (cls === $rt_doublecls()) {
clsName = "double";
} else {
clsName = cls.$meta ? cls.$meta.name : "@" + cls.name;
clsName = cls.$meta ? (cls.$meta.name || ("a/" + cls.name)) : "@" + cls.name;
}
while (arrayDegree-- > 0) {
clsName += "[]";
@ -661,6 +649,9 @@ function Long(lo, hi) {
this.lo = lo | 0;
this.hi = hi | 0;
}
Long.prototype.__teavm_class__ = function() {
return "long";
};
Long.prototype.toString = function() {
var result = [];
var n = this;
@ -677,6 +668,9 @@ Long.prototype.toString = function() {
result = result.reverse().join('');
return positive ? result : "-" + result;
};
Long.prototype.valueOf = function() {
return Long_toNumber(this);
};
var Long_ZERO = new Long(0, 0);
var Long_MAX_NORMAL = 1 << 18;
function Long_fromInt(val) {

View File

@ -18,8 +18,8 @@
<head>
<title>Continuation-passing style demo</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script type="text/javascript" charset="utf-8" src="teavm/classes.js"></script>
<script type="text/javascript" charset="utf-8" src="teavm/stdout.js"></script>
<script type="text/javascript" charset="utf-8" src="teavm/classes.js"></script>
<script type="text/javascript" charset="utf-8" src="highlight.pack.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="syntax.css">

View File

@ -1,5 +1,6 @@
function $rt_putStdout(ch) {
if (ch == 0xA) {
var $rt_stdoutBuffer = "";
function $rt_putStdoutCustom(ch) {
if (ch === 0xA) {
var lineElem = document.createElement("div");
var stdoutElem = document.getElementById("stdout");
lineElem.appendChild(document.createTextNode($rt_stdoutBuffer));

View File

@ -21,7 +21,6 @@ import org.apache.commons.io.output.ByteArrayOutputStream;
import org.junit.Test;
import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.tooling.TeaVMToolLog;
import org.teavm.vm.TeaVM;
@ -76,8 +75,9 @@ public class ClassValueTest {
private DependencyInfo runTest(String methodName) {
TeaVM vm = new TeaVMBuilder(new JavaScriptTarget()).build();
vm.add(new DependencyTestPatcher(getClass().getName(), methodName));
vm.installPlugins();
vm.entryPoint(new MethodReference(getClass().getName(), methodName, ValueType.VOID));
vm.entryPoint(getClass().getName());
vm.build(fileName -> new ByteArrayOutputStream(), "tmp");
if (!vm.getProblemProvider().getSevereProblems().isEmpty()) {
fail("Code compiled with errors:\n" + describeProblems(vm));

View File

@ -123,11 +123,12 @@ public class DependencyTest {
return TeaVMProgressFeedback.CONTINUE;
}
});
vm.add(new DependencyTestPatcher(DependencyTestData.class.getName(), testName.getMethodName()));
vm.installPlugins();
MethodReference testMethod = new MethodReference(DependencyTestData.class,
testName.getMethodName(), void.class);
vm.entryPoint(testMethod).withValue(0, DependencyTestData.class.getName());
vm.entryPoint(DependencyTestData.class.getName());
vm.build(fileName -> new ByteArrayOutputStream(), "out");
List<Problem> problems = vm.getProblemProvider().getSevereProblems();

View File

@ -16,15 +16,18 @@
package org.teavm.dependency;
public class DependencyTestData {
public void virtualCall() {
private DependencyTestData() {
}
public static void virtualCall() {
MetaAssertions.assertTypes(getI(0).foo(), String.class, Integer.class, Class.class);
}
public void instanceOf() {
public static void instanceOf() {
MetaAssertions.assertTypes((String) getI(0).foo(), String.class);
}
public void catchException() throws Exception {
public static void catchException() throws Exception {
try {
throw createException(0);
} catch (IndexOutOfBoundsException e) {
@ -34,7 +37,7 @@ public class DependencyTestData {
}
}
public void propagateException() {
public static void propagateException() {
try {
catchException();
} catch (Throwable e) {
@ -42,12 +45,12 @@ public class DependencyTestData {
}
}
public void arrays() {
public static void arrays() {
Object[] array = { new String("123"), new Integer(123), String.class };
MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
}
public void arraysPassed() {
public static void arraysPassed() {
Object[] array = new Object[3];
fillArray(array);
MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
@ -58,7 +61,7 @@ public class DependencyTestData {
MetaAssertions.assertTypes(array2[0], Long.class, RuntimeException.class);
}
public void arraysRetrieved() {
public static void arraysRetrieved() {
Object[] array = createArray();
MetaAssertions.assertTypes(array[0], String.class, Integer.class, Class.class);
@ -70,24 +73,24 @@ public class DependencyTestData {
static Object[] staticArrayField;
private Object[] createArray() {
private static Object[] createArray() {
Object[] array = new Object[3];
fillArray(array);
return array;
}
private void fillArray(Object[] array) {
private static void fillArray(Object[] array) {
array[0] = "123";
array[1] = 123;
array[2] = String.class;
}
private void fillStaticArray() {
private static void fillStaticArray() {
staticArrayField[0] = 42L;
staticArrayField[0] = new RuntimeException();
}
private I getI(int index) {
private static I getI(int index) {
switch (index) {
case 0:
return new A();
@ -98,7 +101,7 @@ public class DependencyTestData {
}
}
private Exception createException(int index) {
private static Exception createException(int index) {
switch (index) {
case 0:
throw new IndexOutOfBoundsException();
@ -115,21 +118,21 @@ public class DependencyTestData {
Object foo();
}
class A implements I {
static class A implements I {
@Override
public Object foo() {
return "123";
}
}
class B implements I {
static class B implements I {
@Override
public Object foo() {
return Object.class;
}
}
class C implements I {
static class C implements I {
@Override
public Object foo() {
return 123;

View File

@ -0,0 +1,65 @@
/*
* 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.dependency;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.AccessLevel;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
public class DependencyTestPatcher implements ClassHolderTransformer {
private String className;
private String methodName;
public DependencyTestPatcher(String className, String methodName) {
this.className = className;
this.methodName = methodName;
}
@Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
if (cls.getName().equals(className)) {
MethodHolder method = new MethodHolder("main", ValueType.parse(String[].class), ValueType.VOID);
method.setLevel(AccessLevel.PUBLIC);
method.getModifiers().add(ElementModifier.STATIC);
Program program = new Program();
program.createVariable();
program.createVariable();
BasicBlock block = program.createBasicBlock();
method.setProgram(program);
InvokeInstruction invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(new MethodReference(className, methodName, ValueType.VOID));
block.add(invoke);
block.add(new ExitInstruction());
cls.addMethod(method);
}
}
}

View File

@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
import java.util.List;
import org.junit.Test;
import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.dependency.DependencyTestPatcher;
import org.teavm.diagnostics.Problem;
import org.teavm.jso.JSBody;
import org.teavm.model.MethodReference;
@ -94,8 +95,9 @@ public class JSOTest {
private List<Problem> build(String methodName) {
TeaVM vm = new TeaVMBuilder(new JavaScriptTarget()).build();
vm.add(new DependencyTestPatcher(JSOTest.class.getName(), methodName));
vm.installPlugins();
vm.entryPoint("org/teavm/metaprogramming/test", new MethodReference(JSOTest.class, methodName, void.class));
vm.entryPoint(JSOTest.class.getName());
vm.build(name -> new ByteArrayOutputStream(), "tmp");
return vm.getProblemProvider().getSevereProblems();
}

View File

@ -58,38 +58,25 @@ function appendFiles(files, index, callback, errorCallback) {
}
function launchTest(callback) {
$rt_startThread(() => {
let thread = $rt_nativeThread();
let instance;
let message;
if (thread.isResuming()) {
instance = thread.pop();
}
try {
runTest();
} catch (e) {
message = buildErrorMessage(e);
main([], result => {
if (result instanceof Error) {
callback({
status: "failed",
errorMessage: buildErrorMessage(e)
});
return;
}
if (thread.isSuspending()) {
thread.push(instance);
} else {
callback({ status: "OK" });
}
});
function buildErrorMessage(e) {
let stack = e.stack;
let stack = "";
if (e.$javaException && e.$javaException.constructor.$meta) {
stack = e.$javaException.constructor.$meta.name + ": ";
let exceptionMessage = extractException(e.$javaException);
stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
stack += e.$javaException.getMessage();
stack += "\n";
}
stack += "\n" + stack;
stack += e.stack;
return stack;
}
}

View File

@ -43,10 +43,13 @@ import org.teavm.chromerdp.data.Response;
import org.teavm.chromerdp.data.ScopeDTO;
import org.teavm.chromerdp.messages.CallFunctionCommand;
import org.teavm.chromerdp.messages.CallFunctionResponse;
import org.teavm.chromerdp.messages.CompileScriptCommand;
import org.teavm.chromerdp.messages.CompileScriptResponse;
import org.teavm.chromerdp.messages.ContinueToLocationCommand;
import org.teavm.chromerdp.messages.GetPropertiesCommand;
import org.teavm.chromerdp.messages.GetPropertiesResponse;
import org.teavm.chromerdp.messages.RemoveBreakpointCommand;
import org.teavm.chromerdp.messages.RunScriptCommand;
import org.teavm.chromerdp.messages.ScriptParsedNotification;
import org.teavm.chromerdp.messages.SetBreakpointCommand;
import org.teavm.chromerdp.messages.SetBreakpointResponse;
@ -107,9 +110,26 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
}
private void injectFunctions(int contextId) {
callMethod("Runtime.enable", void.class, null);
CompileScriptCommand compileParams = new CompileScriptCommand();
compileParams.expression = "$dbg_class = function(obj) { return typeof obj === 'object' && obj != null "
+ "? obj.__teavm_class__() : null };";
compileParams.sourceURL = "file://fake";
compileParams.persistScript = true;
compileParams.executionContextId = contextId;
CompileScriptResponse response = callMethod("Runtime.compileScript", CompileScriptResponse.class,
compileParams);
RunScriptCommand runParams = new RunScriptCommand();
runParams.scriptId = response.scriptId;
callMethod("Runtime.runScript", void.class, runParams);
}
private ChromeRDPExchangeListener exchangeListener = this::receiveMessage;
private void receiveMessage(final String messageText) {
private void receiveMessage(String messageText) {
new Thread(() -> {
try {
JsonNode jsonMessage = mapper.readTree(messageText);
@ -188,9 +208,9 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.scriptAdded(params.getUrl());
}
injectFunctions(params.getExecutionContextId());
}
@Override
public void addListener(JavaScriptDebuggerListener listener) {
listeners.put(listener, dummy);
@ -203,65 +223,34 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
@Override
public void suspend() {
if (exchange == null) {
return;
}
Message message = new Message();
message.setMethod("Debugger.pause");
sendMessage(message);
callMethod("Debugger.pause", void.class, null);
}
@Override
public void resume() {
if (exchange == null) {
return;
}
Message message = new Message();
message.setMethod("Debugger.resume");
sendMessage(message);
callMethod("Debugger.resume", void.class, null);
}
@Override
public void stepInto() {
if (exchange == null) {
return;
}
Message message = new Message();
message.setMethod("Debugger.stepInto");
sendMessage(message);
callMethod("Debugger.stepInto", void.class, null);
}
@Override
public void stepOut() {
if (exchange == null) {
return;
}
Message message = new Message();
message.setMethod("Debugger.stepOut");
sendMessage(message);
callMethod("Debugger.stepOut", void.class, null);
}
@Override
public void stepOver() {
if (exchange == null) {
return;
}
Message message = new Message();
message.setMethod("Debugger.stepOver");
sendMessage(message);
callMethod("Debugger.stepOver", void.class, null);
}
@Override
public void continueToLocation(JavaScriptLocation location) {
if (exchange == null) {
return;
}
Message message = new Message();
message.setMethod("Debugger.continueToLocation");
ContinueToLocationCommand params = new ContinueToLocationCommand();
params.setLocation(unmap(location));
message.setParams(mapper.valueToTree(params));
sendMessage(message);
callMethod("Debugger.continueToLocation", void.class, params);
}
@Override
@ -341,12 +330,9 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
if (logger.isInfoEnabled()) {
logger.info("Removing breakpoint at {}", breakpoint.getLocation());
}
Message message = new Message();
message.setMethod("Debugger.removeBreakpoint");
RemoveBreakpointCommand params = new RemoveBreakpointCommand();
params.setBreakpointId(breakpoint.chromeId);
message.setParams(mapper.valueToTree(params));
sendMessage(message);
callMethod("Debugger.removeBreakpoint", void.class, params);
}
breakpoint.debugger = null;
breakpoint.chromeId = null;
@ -356,152 +342,79 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
private void updateBreakpoint(final RDPBreakpoint breakpoint) {
if (exchange == null || breakpoint.chromeId != null) {
if (breakpoint.chromeId != null) {
return;
}
final Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod("Debugger.setBreakpoint");
SetBreakpointCommand params = new SetBreakpointCommand();
params.setLocation(unmap(breakpoint.getLocation()));
message.setParams(mapper.valueToTree(params));
if (logger.isInfoEnabled()) {
logger.info("Setting breakpoint at {}, message id is ", breakpoint.getLocation(), message.getId());
logger.info("Setting breakpoint at {}", breakpoint.getLocation());
}
setResponseHandler(message.getId(), (node, out) -> {
if (breakpoint.chromeId != null) {
breakpointsByChromeId.remove(breakpoint.chromeId);
}
if (node != null) {
SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node);
breakpoint.updating.set(true);
try {
SetBreakpointResponse response = callMethod("Debugger.setBreakpoint", SetBreakpointResponse.class, params);
if (response == null) {
breakpoint.chromeId = response.getBreakpointId();
if (breakpoint.chromeId != null) {
breakpointsByChromeId.put(breakpoint.chromeId, breakpoint);
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("Error setting breakpoint at {}, message id is {}",
breakpoint.getLocation(), message.getId());
logger.warn("Error setting breakpoint at {}", breakpoint.getLocation());
}
breakpoint.chromeId = null;
}
} finally {
synchronized (breakpoint.updateMonitor) {
breakpoint.updating.set(false);
breakpoint.updateMonitor.notifyAll();
}
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.breakpointChanged(breakpoint);
}
});
breakpoint.updating.set(true);
sendMessage(message);
}
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.breakpointChanged(breakpoint);
}
}
List<RDPLocalVariable> getScope(String scopeId) {
if (exchange == null) {
return Collections.emptyList();
}
Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod("Runtime.getProperties");
GetPropertiesCommand params = new GetPropertiesCommand();
params.setObjectId(scopeId);
params.setOwnProperties(true);
message.setParams(mapper.valueToTree(params));
CompletableFuture<List<RDPLocalVariable>> sync = setResponseHandler(message.getId(), (node, out) -> {
if (node == null) {
out.complete(Collections.emptyList());
} else {
GetPropertiesResponse response = mapper.reader(GetPropertiesResponse.class).readValue(node);
out.complete(parseProperties(response.getResult()));
}
});
sendMessage(message);
try {
return read(sync);
} catch (InterruptedException | TimeoutException e) {
GetPropertiesResponse response = callMethod("Runtime.getProperties", GetPropertiesResponse.class, params);
if (response == null) {
return Collections.emptyList();
}
return parseProperties(response.getResult());
}
String getClassName(String objectId) {
if (exchange == null) {
return null;
}
Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod("Runtime.callFunctionOn");
CallFunctionCommand params = new CallFunctionCommand();
CallArgumentDTO arg = new CallArgumentDTO();
arg.setObjectId(objectId);
params.setObjectId(objectId);
params.setArguments(new CallArgumentDTO[] { arg });
params.setFunctionDeclaration("$dbg_class");
message.setParams(mapper.valueToTree(params));
CompletableFuture<String> sync = setResponseHandler(message.getId(), (node, out) -> {
if (node == null) {
out.complete("");
} else {
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
RemoteObjectDTO result = response.getResult();
out.complete(result.getValue() != null ? result.getValue().getTextValue() : "");
}
});
sendMessage(message);
try {
String result = read(sync);
return result.isEmpty() ? null : result;
} catch (InterruptedException e) {
return null;
} catch (TimeoutException e) {
return "<timed out>";
}
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
RemoteObjectDTO result = response != null ? response.getResult() : null;
return result.getValue() != null ? result.getValue().getTextValue() : null;
}
String getRepresentation(String objectId) {
if (exchange == null) {
return null;
}
Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod("Runtime.callFunctionOn");
CallFunctionCommand params = new CallFunctionCommand();
CallArgumentDTO arg = new CallArgumentDTO();
arg.setObjectId(objectId);
params.setObjectId(objectId);
params.setArguments(new CallArgumentDTO[] { arg });
params.setFunctionDeclaration("$dbg_repr");
message.setParams(mapper.valueToTree(params));
CompletableFuture<RepresentationWrapper> sync = setResponseHandler(message.getId(), (node, out) -> {
if (node == null) {
out.complete(new RepresentationWrapper(null));
} else {
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
RemoteObjectDTO result = response.getResult();
out.complete(new RepresentationWrapper(result.getValue() != null
? result.getValue().getTextValue() : null));
}
});
sendMessage(message);
try {
RepresentationWrapper result = read(sync);
return result.repr;
} catch (InterruptedException e) {
return null;
} catch (TimeoutException e) {
return "<timed out>";
}
}
static class RepresentationWrapper {
String repr;
RepresentationWrapper(String repr) {
super();
this.repr = repr;
}
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
RemoteObjectDTO result = response != null ? response.getResult() : null;
return result.getValue() != null ? result.getValue().getTextValue() : null;
}
private List<RDPLocalVariable> parseProperties(PropertyDescriptorDTO[] properties) {
@ -585,6 +498,36 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return dto;
}
private <R> R callMethod(String method, Class<R> returnType, Object params) {
if (exchange == null) {
return null;
}
Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod(method);
if (params != null) {
message.setParams(mapper.valueToTree(params));
}
CompletableFuture<R> sync = setResponseHandler(message.getId(), (node, out) -> {
if (node == null) {
out.complete(null);
} else {
R response = returnType != void.class ? mapper.reader(returnType).readValue(node) : null;
out.complete(response);
}
});
sendMessage(message);
try {
return read(sync);
} catch (InterruptedException e) {
return null;
} catch (TimeoutException e) {
logger.warn("Chrome debug protocol: timed out", e);
return null;
}
}
@SuppressWarnings("unchecked")
private <T> CompletableFuture<T> setResponseHandler(int messageId, ResponseHandler<T> handler) {
CompletableFuture<T> future = new CompletableFuture<>();

View File

@ -20,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
import org.teavm.debugging.javascript.JavaScriptLocation;
public class RDPBreakpoint implements JavaScriptBreakpoint {
class RDPBreakpoint implements JavaScriptBreakpoint {
volatile String chromeId;
ChromeRDPDebugger debugger;
private JavaScriptLocation location;

View File

@ -19,7 +19,7 @@ import java.util.Collections;
import java.util.Map;
import org.teavm.debugging.javascript.*;
public class RDPCallFrame implements JavaScriptCallFrame {
class RDPCallFrame implements JavaScriptCallFrame {
private JavaScriptDebugger debugger;
private String chromeId;
private JavaScriptLocation location;
@ -27,7 +27,7 @@ public class RDPCallFrame implements JavaScriptCallFrame {
private JavaScriptValue thisObject;
private JavaScriptValue closure;
public RDPCallFrame(JavaScriptDebugger debugger, String chromeId, JavaScriptLocation location,
RDPCallFrame(JavaScriptDebugger debugger, String chromeId, JavaScriptLocation location,
Map<String, ? extends JavaScriptVariable> variables, JavaScriptValue thisObject,
JavaScriptValue closure) {
this.debugger = debugger;

View File

@ -18,11 +18,11 @@ package org.teavm.chromerdp;
import org.teavm.debugging.javascript.JavaScriptValue;
import org.teavm.debugging.javascript.JavaScriptVariable;
public class RDPLocalVariable implements JavaScriptVariable {
class RDPLocalVariable implements JavaScriptVariable {
private String name;
private RDPValue value;
public RDPLocalVariable(String name, RDPValue value) {
RDPLocalVariable(String name, RDPValue value) {
this.name = name;
this.value = value;
}

View File

@ -21,12 +21,12 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
public class RDPScope extends AbstractMap<String, RDPLocalVariable> {
class RDPScope extends AbstractMap<String, RDPLocalVariable> {
private AtomicReference<Map<String, RDPLocalVariable>> backingMap = new AtomicReference<>();
private ChromeRDPDebugger debugger;
private String id;
public RDPScope(ChromeRDPDebugger debugger, String id) {
RDPScope(ChromeRDPDebugger debugger, String id) {
this.debugger = debugger;
this.id = id;
}

View File

@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.teavm.debugging.javascript.JavaScriptValue;
import org.teavm.debugging.javascript.JavaScriptVariable;
public class RDPValue implements JavaScriptValue {
class RDPValue implements JavaScriptValue {
private AtomicReference<String> representation = new AtomicReference<>();
private AtomicReference<String> className = new AtomicReference<>();
private String typeName;
@ -30,15 +30,14 @@ public class RDPValue implements JavaScriptValue {
private Map<String, ? extends JavaScriptVariable> properties;
private boolean innerStructure;
public RDPValue(ChromeRDPDebugger debugger, String representation, String typeName, String objectId,
RDPValue(ChromeRDPDebugger debugger, String representation, String typeName, String objectId,
boolean innerStructure) {
this.representation.set(representation == null && objectId == null ? "" : representation);
this.typeName = typeName;
this.debugger = debugger;
this.objectId = objectId;
this.innerStructure = innerStructure;
properties = objectId != null ? new RDPScope(debugger, objectId)
: Collections.<String, RDPLocalVariable>emptyMap();
properties = objectId != null ? new RDPScope(debugger, objectId) : Collections.emptyMap();
}
@Override

View File

@ -0,0 +1,26 @@
/*
* 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.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CompileScriptCommand {
public String expression;
public String sourceURL;
public boolean persistScript;
public int executionContextId;
}

View File

@ -0,0 +1,23 @@
/*
* 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.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CompileScriptResponse {
public String scriptId;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 Alexey Andreev.
* 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.
@ -13,13 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.junit;
package org.teavm.chromerdp.messages;
final class ExceptionHelper {
private ExceptionHelper() {
}
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
public static String showException(Throwable e) {
return e.getMessage();
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class RunScriptCommand {
public String scriptId;
}

View File

@ -21,6 +21,7 @@ import org.codehaus.jackson.annotate.JsonIgnoreProperties;
public class ScriptParsedNotification {
private String scriptId;
private String url;
private int executionContextId;
public String getScriptId() {
return scriptId;
@ -37,4 +38,12 @@ public class ScriptParsedNotification {
public void setUrl(String url) {
this.url = url;
}
public int getExecutionContextId() {
return executionContextId;
}
public void setExecutionContextId(int executionContextId) {
this.executionContextId = executionContextId;
}
}

View File

@ -46,7 +46,6 @@ import org.teavm.model.ClassHolderSource;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.PreOptimizingClassHolderSource;
@ -382,11 +381,7 @@ public class TeaVMTool implements BaseTeaVMTool {
vm.add(transformer);
}
if (mainClass != null) {
MethodDescriptor mainMethodDesc = new MethodDescriptor("main", String[].class, void.class);
vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc))
.withValue(1, "[java.lang.String")
.withArrayValue(1, "java.lang.String")
.async();
vm.entryPoint(mainClass);
}
for (String className : classesToPreserve) {
vm.preserveType(className);
@ -418,7 +413,7 @@ public class TeaVMTool implements BaseTeaVMTool {
if (targetType == TeaVMTargetType.JAVASCRIPT) {
try (OutputStream output = new FileOutputStream(new File(targetDirectory, outputName), true)) {
try (Writer writer = new OutputStreamWriter(output, "UTF-8")) {
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
additionalJavaScriptOutput(writer);
}
}
@ -458,10 +453,6 @@ public class TeaVMTool implements BaseTeaVMTool {
}
private void additionalJavaScriptOutput(Writer writer) throws IOException {
if (mainClass != null) {
writer.append("main = $rt_mainStarter(main);\n");
}
if (debugInformationGenerated) {
assert debugEmitter != null;
DebugInformation debugInfo = debugEmitter.getDebugInformation();

View File

@ -60,7 +60,6 @@ import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.PreOptimizingClassHolderSource;
import org.teavm.model.ValueType;
import org.teavm.parsing.ClasspathClassHolderSource;
@ -432,24 +431,21 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private CompileResult compileToJs(Method method, TeaVMTestConfiguration<JavaScriptTarget> configuration,
File path) {
return compileTest(method, configuration, JavaScriptTarget::new, vm -> {
MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException",
Throwable.class, String.class);
vm.entryPoint("runTest", new MethodReference(TestEntryPoint.class, "run", void.class)).async();
vm.entryPoint("extractException", exceptionMsg);
vm.entryPoint(TestEntryPoint.class.getName());
}, path, ".js");
}
private CompileResult compileToC(Method method, TeaVMTestConfiguration<CTarget> configuration,
File path) {
return compileTest(method, configuration, CTarget::new, vm -> {
vm.entryPoint("main", new MethodReference(TestEntryPoint.class, "main", String[].class, void.class));
vm.entryPoint(TestNativeEntryPoint.class.getName());
}, path, ".c");
}
private CompileResult compileToWasm(Method method, TeaVMTestConfiguration<WasmTarget> configuration,
File path) {
return compileTest(method, configuration, WasmTarget::new, vm -> {
vm.entryPoint("main", new MethodReference(TestEntryPoint.class, "main", String[].class, void.class));
vm.entryPoint(TestNativeEntryPoint.class.getName());
}, path, ".wasm");
}

View File

@ -33,13 +33,7 @@ final class TestEntryPoint {
private static native boolean isExpectedException(Class<?> cls);
public static void main(String[] args) throws Exception {
try {
run();
System.out.println("SUCCESS");
} catch (Throwable e) {
e.printStackTrace(System.out);
System.out.println("FAILURE");
}
public static void main(String[] args) throws Throwable {
run();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 Alexey Andreev.
* 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.
@ -15,50 +15,33 @@
*/
package org.teavm.junit;
import static org.teavm.junit.TestExceptionPlugin.GET_MESSAGE;
import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyNode;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
class TestExceptionDependency extends AbstractDependencyListener {
private MethodReference getMessageRef = new MethodReference(ExceptionHelper.class, "showException",
Throwable.class, String.class);
class TestExceptionDependencyListener extends AbstractDependencyListener {
private DependencyNode allClasses;
@Override
public void started(DependencyAgent agent) {
allClasses = agent.createNode();
allClasses.addConsumer(c -> {
if (agent.getClassSource().isSuperType("java.lang.Throwable", c.getName()).orElse(false)) {
MethodDependency methodDep = agent.linkMethod(new MethodReference(c.getName(), GET_MESSAGE), null);
methodDep.getVariable(0).propagate(c);
methodDep.use();
}
});
agent.linkClass("java.lang.Throwable", null);
}
@Override
public void classReached(DependencyAgent agent, String className, CallLocation location) {
if (isException(agent.getClassSource(), className)) {
allClasses.propagate(agent.getType(className));
}
}
private boolean isException(ClassReaderSource classSource, String className) {
while (className != null) {
if (className.equals("java.lang.Throwable")) {
return true;
}
ClassReader cls = classSource.get(className);
if (cls == null) {
return false;
}
className = cls.getParent();
}
return false;
}
@Override
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
if (method.getReference().equals(getMessageRef)) {
allClasses.connect(method.getVariable(1));
}
allClasses.propagate(agent.getType(className));
}
}

View File

@ -15,12 +15,58 @@
*/
package org.teavm.junit;
import java.io.IOException;
import org.teavm.backend.javascript.TeaVMJavaScriptHost;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.RenderingManager;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.ValueType;
import org.teavm.vm.BuildTarget;
import org.teavm.vm.spi.AbstractRendererListener;
import org.teavm.vm.spi.TeaVMHost;
import org.teavm.vm.spi.TeaVMPlugin;
class TestExceptionPlugin implements TeaVMPlugin {
static final MethodDescriptor GET_MESSAGE = new MethodDescriptor("getMessage", ValueType.parse(String.class));
@Override
public void install(TeaVMHost host) {
host.add(new TestExceptionDependency());
host.add(new TestExceptionDependencyListener());
TeaVMJavaScriptHost jsHost = host.getExtension(TeaVMJavaScriptHost.class);
if (jsHost != null) {
install(jsHost);
}
}
private void install(TeaVMJavaScriptHost host) {
host.addVirtualMethods((context, methodRef) -> {
if (!methodRef.getDescriptor().equals(GET_MESSAGE)) {
return false;
}
return context.getClassSource().isSuperType("java.lang.Throwable", methodRef.getClassName()).orElse(false);
});
host.add(new AbstractRendererListener() {
RenderingManager manager;
@Override
public void begin(RenderingManager manager, BuildTarget buildTarget) throws IOException {
this.manager = manager;
}
@Override
public void complete() throws IOException {
renderExceptionMessage(manager.getWriter());
}
});
}
private void renderExceptionMessage(SourceWriter writer) throws IOException {
writer.appendClass("java.lang.Throwable").append(".prototype.getMessage").ws().append("=").ws()
.append("function()").ws().append("{").indent().softNewLine();
writer.append("return $rt_ustr(this.").appendMethod("getMessage", String.class).append("());")
.softNewLine();
writer.outdent().append("};").newLine();
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.junit;
final class TestNativeEntryPoint {
private TestNativeEntryPoint() {
}
public static void main(String[] args) throws Exception {
try {
TestEntryPoint.run();
System.out.println("SUCCESS");
} catch (Throwable e) {
e.printStackTrace(System.out);
System.out.println("FAILURE");
}
}
}

View File

@ -1,45 +1,22 @@
function main(callback) {
$rt_startThread(function () {
var thread = $rt_nativeThread();
var instance;
var ptr = 0;
var message;
if (thread.isResuming()) {
ptr = thread.pop();
instance = thread.pop();
}
loop: while (true) {
switch (ptr) {
case 0:
try {
runTest();
} catch (e) {
message = {};
makeErrorMessage(message, e);
break loop;
}
if (thread.isSuspending()) {
thread.push(instance);
thread.push(ptr);
return;
}
message = {};
message.status = "ok";
break loop;
}
function runMain(callback) {
main([], function(result) {
var message = {};
if (result instanceof Error) {
makeErrorMessage(message, result);
} else {
message.status = "ok";
}
callback.complete(JSON.stringify(message));
});
function makeErrorMessage(message, e) {
message.status = "exception";
var stack = e.stack;
var stack = "";
if (e.$javaException && e.$javaException.constructor.$meta) {
message.exception = e.$javaException.constructor.$meta.name;
message.stack = e.$javaException.constructor.$meta.name + ": ";
var exceptionMessage = extractException(e.$javaException);
message.stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
stack = e.$javaException.constructor.$meta.name + ": ";
stack += e.$javaException.getMessage() || "";
stack += "\n";
}
message.stack += "\n" + stack;
message.stack = stack + e.stack;
}
}

View File

@ -1,52 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>TeaVM JUnit test</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript">
$rt_startThread(function() {
var thread = $rt_nativeThread();
var instance;
var ptr = 0;
var message;
if (thread.isResuming()) {
ptr = thread.pop();
instance = thread.pop();
}
loop: while (true) {
switch (ptr) {
case 0:
try {
runTest();
} catch (e) {
message = buildErrorMessage(e);
break loop;
}
if (thread.isSuspending()) {
thread.push(instance);
thread.push(ptr);
return;
}
message = "OK";
break loop;
}
}
document.body.appendChild(document.createTextNode(message));
});
<head>
<title>TeaVM JUnit test</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript">
main([], function(result) {
var message = result instanceof Error ? buildErrorMessage(result) : "OK";
document.body.appendChild(document.createTextNode(message))
});
function buildErrorMessage(e) {
var stack = e.stack;
function buildErrorMessage(e) {
var stack = "";
if (e.$javaException && e.$javaException.constructor.$meta) {
stack = e.$javaException.constructor.$meta.name + ": ";
var exceptionMessage = extractException(e.$javaException);
stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
stack = e.$javaException.constructor.$meta.name + ": ";
stack += e.$javaException.getMessage() || "";
stack += "\n";
}
stack += "\n" + stack;
stack += e.stack;
return stack;
}
</script>
</body>
}
</script>
</body>
</html>

View File

@ -14,44 +14,23 @@
* limitations under the License.
*/
$rt_startThread(function() {
var thread = $rt_nativeThread();
var instance;
var ptr = 0;
var message;
if (thread.isResuming()) {
ptr = thread.pop();
instance = thread.pop();
}
loop: while (true) { switch (ptr) {
case 0:
try {
runTest();
} catch (e) {
message = {};
makeErrorMessage(message, e);
break loop;
}
if (thread.isSuspending()) {
thread.push(instance);
thread.push(ptr);
return;
}
message = {};
main([], function(result) {
var message = {};
if (result instanceof Error) {
makeErrorMessage(message, result);
} else {
message.status = "ok";
break loop;
}}
}
window.parent.postMessage(JSON.stringify(message), "*");
});
function makeErrorMessage(message, e) {
message.status = "exception";
var stack = e.stack;
var stack = "";
if (e.$javaException && e.$javaException.constructor.$meta) {
message.exception = e.$javaException.constructor.$meta.name;
message.stack = e.$javaException.constructor.$meta.name + ": ";
var exceptionMessage = extractException(e.$javaException);
message.stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
stack = e.$javaException.constructor.$meta.name + ": ";
stack += e.$javaException.getMessage() || "";
stack += "\n";
}
message.stack += "\n" + stack;
};
message.stack = stack + e.stack;
}