From 148c07336c99802782020895d56e48f55eb48ee7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 15 Nov 2018 12:47:25 +0300 Subject: [PATCH] Generate entire code inside wrapper IIF --- .../java/org/teavm/backend/c/CTarget.java | 2 +- .../backend/javascript/JavaScriptTarget.java | 34 ++- .../javascript/rendering/Renderer.java | 34 +++ .../org/teavm/backend/wasm/WasmTarget.java | 2 +- .../information/DebugInformation.java | 1 - core/src/main/java/org/teavm/vm/TeaVM.java | 83 +++---- .../java/org/teavm/vm/TeaVMEntryPoint.java | 91 +------ .../org/teavm/backend/javascript/runtime.js | 34 ++- samples/async/src/main/webapp/index.html | 2 +- samples/async/src/main/webapp/teavm/stdout.js | 5 +- .../org/teavm/dependency/ClassValueTest.java | 4 +- .../org/teavm/dependency/DependencyTest.java | 3 +- .../teavm/dependency/DependencyTestData.java | 33 +-- .../dependency/DependencyTestPatcher.java | 65 +++++ .../test/java/org/teavm/tests/JSOTest.java | 4 +- tests/src/test/js/frame.js | 25 +- .../teavm/chromerdp/ChromeRDPDebugger.java | 225 +++++++----------- .../org/teavm/chromerdp/RDPBreakpoint.java | 2 +- .../org/teavm/chromerdp/RDPCallFrame.java | 4 +- .../org/teavm/chromerdp/RDPLocalVariable.java | 4 +- .../java/org/teavm/chromerdp/RDPScope.java | 4 +- .../java/org/teavm/chromerdp/RDPValue.java | 7 +- .../messages/CompileScriptCommand.java | 26 ++ .../messages/CompileScriptResponse.java | 23 ++ .../chromerdp/messages/RunScriptCommand.java} | 14 +- .../messages/ScriptParsedNotification.java | 9 + .../java/org/teavm/tooling/TeaVMTool.java | 13 +- .../java/org/teavm/junit/TeaVMTestRunner.java | 10 +- .../java/org/teavm/junit/TestEntryPoint.java | 10 +- ...a => TestExceptionDependencyListener.java} | 43 +--- .../org/teavm/junit/TestExceptionPlugin.java | 48 +++- .../org/teavm/junit/TestNativeEntryPoint.java | 31 +++ .../main/resources/teavm-htmlunit-adapter.js | 47 +--- .../src/main/resources/teavm-run-test.html | 65 ++--- .../main/resources/teavm-selenium-adapter.js | 45 +--- 35 files changed, 520 insertions(+), 532 deletions(-) create mode 100644 tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java create mode 100644 tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptCommand.java create mode 100644 tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptResponse.java rename tools/{junit/src/main/java/org/teavm/junit/ExceptionHelper.java => chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RunScriptCommand.java} (71%) rename tools/junit/src/main/java/org/teavm/junit/{TestExceptionDependency.java => TestExceptionDependencyListener.java} (50%) create mode 100644 tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index d254fa977..e4b8433c7 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -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);"); } } diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index ad7f29d1c..79b1d7c10 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -235,6 +235,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { MethodDependency exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( NoClassDefFoundError.class, "", 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, "", @@ -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 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; diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 64d57e28a..5d63a460d 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -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); } diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 9d237aacd..dfd233525 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -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()); diff --git a/core/src/main/java/org/teavm/debugging/information/DebugInformation.java b/core/src/main/java/org/teavm/debugging/information/DebugInformation.java index 1fc27673c..1137d87f9 100644 --- a/core/src/main/java/org/teavm/debugging/information/DebugInformation.java +++ b/core/src/main/java/org/teavm/debugging/information/DebugInformation.java @@ -645,7 +645,6 @@ public class DebugInformation { int[] methods; } - class MethodTree { int[] data; int[] offsets; diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 711589cec..f0a6d3aba 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -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 entryPoints = new HashMap<>(); + private final Map entryPoints = new LinkedHashMap<>(); private final Map readonlyEntryPoints = Collections.unmodifiableMap(entryPoints); private final Set preservedClasses = new HashSet<>(); private final Set readonlyPreservedClasses = Collections.unmodifiableSet(preservedClasses); @@ -243,58 +250,38 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return target.getPlatformTags(); } - /** - *

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.

- * - *

You should call this method after installing all plugins and interceptors, but before - * doing the actual build.

- * - * @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); } - /** - *

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.

- * - *

You should call this method after installing all plugins and interceptors, but before - * doing the actual build.

- * - * @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) { diff --git a/core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java b/core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java index df28d6907..4457a093a 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java +++ b/core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java @@ -15,102 +15,23 @@ */ package org.teavm.vm; -import java.util.HashMap; import org.teavm.dependency.MethodDependency; import org.teavm.model.MethodReference; -/** - *

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.

- * - *

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:

- * - *
{@code
- *static void entryPoint(Map map) {
- *    for (Map.Entry entry : map.entrySet()) {
- *        System.out.println(entry.getKey() + " => " + entry.getValue());
- *    }
- *}}
- * - *

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:

- * - *
{@code
- *vm.preserveType("JavaHashMap", "java.util.HashMap");
- *vm.entryPoint("initJavaHashMap", new MethodReference("java.util.HashMap",
- *        "", 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")
- *}
- * - *

And in JavaScript you would do the following:

- * - *
{@code
- *var map = new JavaHashMap();
- *initJavaHashMap(map);
- *putValueIntoJavaMap(map, $rt_str("foo"), $rt_str("bar"));
- *entryPoint(map);
- *}
- * - *

If you didn't call .withValue(1, "java.util.HashMap"), TeaVM could not know, - * what implementation of entrySet method to include.

- * - * @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(); } } diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 6dcf426a5..b4e8d1fc3 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -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) { diff --git a/samples/async/src/main/webapp/index.html b/samples/async/src/main/webapp/index.html index 4ac1aeefd..552856aa7 100644 --- a/samples/async/src/main/webapp/index.html +++ b/samples/async/src/main/webapp/index.html @@ -18,8 +18,8 @@ Continuation-passing style demo - + diff --git a/samples/async/src/main/webapp/teavm/stdout.js b/samples/async/src/main/webapp/teavm/stdout.js index cb279b2b8..5b72b776e 100644 --- a/samples/async/src/main/webapp/teavm/stdout.js +++ b/samples/async/src/main/webapp/teavm/stdout.js @@ -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)); diff --git a/tests/src/test/java/org/teavm/dependency/ClassValueTest.java b/tests/src/test/java/org/teavm/dependency/ClassValueTest.java index e4e84c477..bb04a803f 100644 --- a/tests/src/test/java/org/teavm/dependency/ClassValueTest.java +++ b/tests/src/test/java/org/teavm/dependency/ClassValueTest.java @@ -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)); diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTest.java b/tests/src/test/java/org/teavm/dependency/DependencyTest.java index ffcb1e338..86577dedb 100644 --- a/tests/src/test/java/org/teavm/dependency/DependencyTest.java +++ b/tests/src/test/java/org/teavm/dependency/DependencyTest.java @@ -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 problems = vm.getProblemProvider().getSevereProblems(); diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTestData.java b/tests/src/test/java/org/teavm/dependency/DependencyTestData.java index ac548cbde..469451aeb 100644 --- a/tests/src/test/java/org/teavm/dependency/DependencyTestData.java +++ b/tests/src/test/java/org/teavm/dependency/DependencyTestData.java @@ -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; diff --git a/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java b/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java new file mode 100644 index 000000000..1c34c728a --- /dev/null +++ b/tests/src/test/java/org/teavm/dependency/DependencyTestPatcher.java @@ -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); + } + } +} diff --git a/tests/src/test/java/org/teavm/tests/JSOTest.java b/tests/src/test/java/org/teavm/tests/JSOTest.java index 413b6db26..9f742abc4 100644 --- a/tests/src/test/java/org/teavm/tests/JSOTest.java +++ b/tests/src/test/java/org/teavm/tests/JSOTest.java @@ -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 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(); } diff --git a/tests/src/test/js/frame.js b/tests/src/test/js/frame.js index 6f0ef2d1d..cbbe3fac3 100644 --- a/tests/src/test/js/frame.js +++ b/tests/src/test/js/frame.js @@ -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; } } diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java index 433d9ca5b..c3cd51380 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java @@ -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 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> 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 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 ""; - } + 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 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 ""; - } - } - 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 parseProperties(PropertyDescriptorDTO[] properties) { @@ -585,6 +498,36 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC return dto; } + private R callMethod(String method, Class 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 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 CompletableFuture setResponseHandler(int messageId, ResponseHandler handler) { CompletableFuture future = new CompletableFuture<>(); diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java index 5987ce198..be1cde013 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java @@ -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; diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java index 29e0d8a58..ca4eccaf8 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java @@ -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 variables, JavaScriptValue thisObject, JavaScriptValue closure) { this.debugger = debugger; diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java index 532aa90bd..ecee1d660 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java @@ -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; } diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java index b14070d07..6680ea0a6 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java @@ -21,12 +21,12 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -public class RDPScope extends AbstractMap { +class RDPScope extends AbstractMap { private AtomicReference> 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; } diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java index 20b8cb3da..54af2fdf0 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java @@ -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 representation = new AtomicReference<>(); private AtomicReference className = new AtomicReference<>(); private String typeName; @@ -30,15 +30,14 @@ public class RDPValue implements JavaScriptValue { private Map 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.emptyMap(); + properties = objectId != null ? new RDPScope(debugger, objectId) : Collections.emptyMap(); } @Override diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptCommand.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptCommand.java new file mode 100644 index 000000000..0946fb9cc --- /dev/null +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptCommand.java @@ -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; +} diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptResponse.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptResponse.java new file mode 100644 index 000000000..d83cb8478 --- /dev/null +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CompileScriptResponse.java @@ -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; +} diff --git a/tools/junit/src/main/java/org/teavm/junit/ExceptionHelper.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RunScriptCommand.java similarity index 71% rename from tools/junit/src/main/java/org/teavm/junit/ExceptionHelper.java rename to tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RunScriptCommand.java index f4ad9ed20..340bff615 100644 --- a/tools/junit/src/main/java/org/teavm/junit/ExceptionHelper.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RunScriptCommand.java @@ -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; } diff --git a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java index 17d25c8bc..3681b91e0 100644 --- a/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java +++ b/tools/chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java @@ -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; + } } diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index deadc4392..c717a67d6 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -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(); diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 707566800..24458bc71 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -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 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 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 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"); } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java index a9ebbbfcf..4960c6e15 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java @@ -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(); } } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependency.java b/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java similarity index 50% rename from tools/junit/src/main/java/org/teavm/junit/TestExceptionDependency.java rename to tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java index 6eab0bbfd..7cb3d0a05 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependency.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependencyListener.java @@ -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)); } } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java b/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java index b274585fe..75c0bcc15 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java @@ -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(); } } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java new file mode 100644 index 000000000..9b6138718 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java @@ -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"); + } + } +} diff --git a/tools/junit/src/main/resources/teavm-htmlunit-adapter.js b/tools/junit/src/main/resources/teavm-htmlunit-adapter.js index 53032f2f7..ed268306e 100644 --- a/tools/junit/src/main/resources/teavm-htmlunit-adapter.js +++ b/tools/junit/src/main/resources/teavm-htmlunit-adapter.js @@ -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; } } \ No newline at end of file diff --git a/tools/junit/src/main/resources/teavm-run-test.html b/tools/junit/src/main/resources/teavm-run-test.html index 2acb8b862..e2d8a8bed 100644 --- a/tools/junit/src/main/resources/teavm-run-test.html +++ b/tools/junit/src/main/resources/teavm-run-test.html @@ -1,52 +1,27 @@ - - TeaVM JUnit test - - - - - + - + } + + \ No newline at end of file diff --git a/tools/junit/src/main/resources/teavm-selenium-adapter.js b/tools/junit/src/main/resources/teavm-selenium-adapter.js index 715c9acdd..9b77aa3dd 100644 --- a/tools/junit/src/main/resources/teavm-selenium-adapter.js +++ b/tools/junit/src/main/resources/teavm-selenium-adapter.js @@ -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; -}; \ No newline at end of file + message.stack = stack + e.stack; +} \ No newline at end of file