diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index f1dbd2ec3..198b36c4f 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -24,7 +24,7 @@ import java.util.ServiceLoader; import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor; import org.teavm.classlib.impl.unicode.CLDRReader; import org.teavm.classlib.java.lang.reflect.AnnotationDependencyListener; -import org.teavm.javascript.target.TeaVMJavaScriptHost; +import org.teavm.javascript.backend.TeaVMJavaScriptHost; import org.teavm.model.MethodReference; import org.teavm.platform.PlatformClass; import org.teavm.vm.spi.TeaVMHost; diff --git a/core/src/main/java/org/teavm/javascript/backend/JavaScriptTarget.java b/core/src/main/java/org/teavm/javascript/backend/JavaScriptTarget.java new file mode 100644 index 000000000..b6b36dc01 --- /dev/null +++ b/core/src/main/java/org/teavm/javascript/backend/JavaScriptTarget.java @@ -0,0 +1,346 @@ +/* + * Copyright 2016 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.javascript.backend; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.ast.ClassNode; +import org.teavm.ast.cache.EmptyRegularMethodNodeCache; +import org.teavm.ast.cache.MethodNodeCache; +import org.teavm.ast.decompilation.Decompiler; +import org.teavm.codegen.AliasProvider; +import org.teavm.codegen.DefaultAliasProvider; +import org.teavm.codegen.DefaultNamingStrategy; +import org.teavm.codegen.MinifyingAliasProvider; +import org.teavm.codegen.SourceWriter; +import org.teavm.codegen.SourceWriterBuilder; +import org.teavm.debugging.information.DebugInformationEmitter; +import org.teavm.debugging.information.SourceLocation; +import org.teavm.dependency.DependencyChecker; +import org.teavm.dependency.MethodDependency; +import org.teavm.javascript.Renderer; +import org.teavm.javascript.RenderingException; +import org.teavm.javascript.spi.GeneratedBy; +import org.teavm.javascript.spi.Generator; +import org.teavm.javascript.spi.InjectedBy; +import org.teavm.javascript.spi.Injector; +import org.teavm.model.BasicBlock; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassHolder; +import org.teavm.model.ElementModifier; +import org.teavm.model.InstructionLocation; +import org.teavm.model.ListableClassHolderSource; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.util.AsyncMethodFinder; +import org.teavm.model.util.ProgramUtils; +import org.teavm.vm.BuildTarget; +import org.teavm.vm.TeaVMEntryPoint; +import org.teavm.vm.TeaVMTarget; +import org.teavm.vm.TeaVMTargetController; +import org.teavm.vm.spi.RendererListener; +import org.teavm.vm.spi.TeaVMHostExtension; + +public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { + private TeaVMTargetController controller; + private boolean minifying = true; + private final Map methodGenerators = new HashMap<>(); + private final Map methodInjectors = new HashMap<>(); + private final List rendererListeners = new ArrayList<>(); + private DebugInformationEmitter debugEmitter; + private MethodNodeCache astCache = new EmptyRegularMethodNodeCache(); + private final Set asyncMethods = new HashSet<>(); + private final Set asyncFamilyMethods = new HashSet<>(); + + @Override + public void setController(TeaVMTargetController controller) { + this.controller = controller; + } + + @Override + public void add(RendererListener listener) { + rendererListeners.add(listener); + } + + @Override + public void add(MethodReference methodRef, Generator generator) { + methodGenerators.put(methodRef, generator); + } + + @Override + public void add(MethodReference methodRef, Injector injector) { + methodInjectors.put(methodRef, injector); + } + + /** + * Reports whether this TeaVM instance uses obfuscation when generating the JavaScript code. + * + * @see #setMinifying(boolean) + * @return whether TeaVM produces obfuscated code. + */ + public boolean isMinifying() { + return minifying; + } + + /** + * Specifies whether this TeaVM instance uses obfuscation when generating the JavaScript code. + * + * @see #isMinifying() + * @param minifying whether TeaVM should obfuscate code. + */ + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public MethodNodeCache getAstCache() { + return astCache; + } + + public void setAstCache(MethodNodeCache methodAstCache) { + this.astCache = methodAstCache; + } + + public DebugInformationEmitter getDebugEmitter() { + return debugEmitter; + } + + public void setDebugEmitter(DebugInformationEmitter debugEmitter) { + this.debugEmitter = debugEmitter; + } + + @Override + public boolean requiresRegisterAllocation() { + return true; + } + + @Override + public List getHostExtensions() { + return Collections.singletonList(this); + } + + @Override + public void contributeDependencies(DependencyChecker dependencyChecker) { + dependencyChecker.linkMethod(new MethodReference(Class.class.getName(), "getClass", + ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null).use(); + dependencyChecker.linkMethod(new MethodReference(String.class, "", char[].class, void.class), + null).use(); + dependencyChecker.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class, char[].class, + int.class, void.class), null).use(); + + MethodDependency internDep = dependencyChecker.linkMethod(new MethodReference(String.class, "intern", + String.class), null); + internDep.getVariable(0).propagate(dependencyChecker.getType("java.lang.String")); + internDep.use(); + + dependencyChecker.linkMethod(new MethodReference(String.class, "length", int.class), null).use(); + dependencyChecker.linkMethod(new MethodReference(Object.class, "clone", Object.class), null).use(); + dependencyChecker.linkMethod(new MethodReference(Thread.class, "currentThread", Thread.class), null).use(); + dependencyChecker.linkMethod(new MethodReference(Thread.class, "getMainThread", Thread.class), null).use(); + dependencyChecker.linkMethod( + new MethodReference(Thread.class, "setCurrentThread", Thread.class, void.class), null).use(); + MethodDependency exceptionCons = dependencyChecker.linkMethod(new MethodReference( + NoClassDefFoundError.class, "", String.class, void.class), null); + + exceptionCons.getVariable(0).propagate(dependencyChecker.getType(NoClassDefFoundError.class.getName())); + exceptionCons.getVariable(1).propagate(dependencyChecker.getType("java.lang.String")); + exceptionCons = dependencyChecker.linkMethod(new MethodReference(NoSuchFieldError.class, "", + String.class, void.class), null); + exceptionCons.use(); + exceptionCons.getVariable(0).propagate(dependencyChecker.getType(NoSuchFieldError.class.getName())); + exceptionCons.getVariable(1).propagate(dependencyChecker.getType("java.lang.String")); + exceptionCons = dependencyChecker.linkMethod(new MethodReference(NoSuchMethodError.class, "", + String.class, void.class), null); + exceptionCons.use(); + exceptionCons.getVariable(0).propagate(dependencyChecker.getType(NoSuchMethodError.class.getName())); + exceptionCons.getVariable(1).propagate(dependencyChecker.getType("java.lang.String")); + } + + @Override + public void emit(ListableClassHolderSource classes, OutputStream output, BuildTarget target) { + try (Writer writer = new OutputStreamWriter(output, "UTF-8")) { + emit(classes, writer, target); + } catch (IOException e) { + throw new RenderingException(e); + } + } + + private void emit(ListableClassHolderSource classes, Writer writer, BuildTarget target) { + List clsNodes = modelToAst(classes); + if (controller.wasCancelled()) { + return; + } + + AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider(); + DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, controller.getUnprocessedClassSource()); + SourceWriterBuilder builder = new SourceWriterBuilder(naming); + builder.setMinified(minifying); + SourceWriter sourceWriter = builder.build(writer); + + Renderer renderer = new Renderer(sourceWriter, classes, controller.getClassLoader(), controller.getServices(), + asyncMethods, asyncFamilyMethods, controller.getDiagnostics()); + renderer.setProperties(controller.getProperties()); + renderer.setMinifying(minifying); + if (debugEmitter != null) { + for (String className : classes.getClassNames()) { + ClassHolder cls = classes.get(className); + for (MethodHolder method : cls.getMethods()) { + if (method.getProgram() != null) { + emitCFG(debugEmitter, method.getProgram()); + } + } + if (controller.wasCancelled()) { + return; + } + } + renderer.setDebugEmitter(debugEmitter); + } + renderer.getDebugEmitter().setLocationProvider(sourceWriter); + for (Map.Entry entry : methodInjectors.entrySet()) { + renderer.addInjector(entry.getKey(), entry.getValue()); + } + try { + for (RendererListener listener : rendererListeners) { + listener.begin(renderer, target); + } + sourceWriter.append("\"use strict\";").newLine(); + renderer.renderRuntime(); + renderer.render(clsNodes); + renderer.renderStringPool(); + 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(); + } + for (Map.Entry entry : controller.getExportedClasses().entrySet()) { + sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws() + .appendClass(entry.getValue()).append(";").newLine(); + } + for (RendererListener listener : rendererListeners) { + listener.complete(); + } + } catch (IOException e) { + throw new RenderingException("IO Error occured", e); + } + } + + private List modelToAst(ListableClassHolderSource classes) { + AsyncMethodFinder asyncFinder = new AsyncMethodFinder(controller.getDependencyInfo().getCallGraph(), + controller.getDiagnostics()); + asyncFinder.find(classes); + asyncMethods.addAll(asyncFinder.getAsyncMethods()); + asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods()); + + Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods); + decompiler.setRegularMethodCache(controller.isIncremental() ? astCache : null); + + for (Map.Entry entry : methodGenerators.entrySet()) { + decompiler.addGenerator(entry.getKey(), entry.getValue()); + } + for (MethodReference injectedMethod : methodInjectors.keySet()) { + decompiler.addMethodToSkip(injectedMethod); + } + List classOrder = decompiler.getClassOrdering(classes.getClassNames()); + List classNodes = new ArrayList<>(); + for (String className : classOrder) { + ClassHolder cls = classes.get(className); + for (MethodHolder method : cls.getMethods()) { + preprocessNativeMethod(method); + if (controller.wasCancelled()) { + break; + } + } + classNodes.add(decompiler.decompile(cls)); + } + return classNodes; + } + + private void preprocessNativeMethod(MethodHolder method) { + if (!method.getModifiers().contains(ElementModifier.NATIVE) + || methodGenerators.get(method.getReference()) != null + || methodInjectors.get(method.getReference()) != null + || method.getAnnotations().get(GeneratedBy.class.getName()) != null + || method.getAnnotations().get(InjectedBy.class.getName()) != null) { + return; + } + method.getModifiers().remove(ElementModifier.NATIVE); + + Program program = new Program(); + method.setProgram(program); + BasicBlock block = program.createBasicBlock(); + Variable exceptionVar = program.createVariable(); + ConstructInstruction newExceptionInsn = new ConstructInstruction(); + newExceptionInsn.setType(NoSuchMethodError.class.getName()); + newExceptionInsn.setReceiver(exceptionVar); + block.getInstructions().add(newExceptionInsn); + + Variable constVar = program.createVariable(); + StringConstantInstruction constInsn = new StringConstantInstruction(); + constInsn.setConstant("Native method implementation not found: " + method.getReference()); + constInsn.setReceiver(constVar); + block.getInstructions().add(constInsn); + + InvokeInstruction initExceptionInsn = new InvokeInstruction(); + initExceptionInsn.setInstance(exceptionVar); + initExceptionInsn.setMethod(new MethodReference(NoSuchMethodError.class, "", String.class, void.class)); + initExceptionInsn.setType(InvocationType.SPECIAL); + initExceptionInsn.getArguments().add(constVar); + block.getInstructions().add(initExceptionInsn); + + RaiseInstruction raiseInsn = new RaiseInstruction(); + raiseInsn.setException(exceptionVar); + block.getInstructions().add(raiseInsn); + + controller.getDiagnostics().error(new CallLocation(method.getReference()), + "Native method {{m0}} has no implementation", method.getReference()); + } + + private void emitCFG(DebugInformationEmitter emitter, Program program) { + Map cfg = ProgramUtils.getLocationCFG(program); + for (Map.Entry entry : cfg.entrySet()) { + SourceLocation location = map(entry.getKey()); + SourceLocation[] successors = new SourceLocation[entry.getValue().length]; + for (int i = 0; i < entry.getValue().length; ++i) { + successors[i] = map(entry.getValue()[i]); + } + emitter.addSuccessors(location, successors); + } + } + + private static SourceLocation map(InstructionLocation location) { + if (location == null) { + return null; + } + return new SourceLocation(location.getFileName(), location.getLine()); + } +} diff --git a/core/src/main/java/org/teavm/javascript/backend/TeaVMJavaScriptHost.java b/core/src/main/java/org/teavm/javascript/backend/TeaVMJavaScriptHost.java new file mode 100644 index 000000000..31665137b --- /dev/null +++ b/core/src/main/java/org/teavm/javascript/backend/TeaVMJavaScriptHost.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 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.javascript.backend; + +import org.teavm.javascript.spi.Generator; +import org.teavm.javascript.spi.Injector; +import org.teavm.model.MethodReference; +import org.teavm.vm.spi.RendererListener; +import org.teavm.vm.spi.TeaVMHostExtension; + +public interface TeaVMJavaScriptHost extends TeaVMHostExtension { + void add(MethodReference methodRef, Generator generator); + + void add(MethodReference methodRef, Injector injector); + + void add(RendererListener listener); +} diff --git a/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java b/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java index 9c935dc2f..a1aef2047 100644 --- a/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java +++ b/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java @@ -15,7 +15,7 @@ */ package org.teavm.html4j; -import org.teavm.javascript.target.TeaVMJavaScriptHost; +import org.teavm.javascript.backend.TeaVMJavaScriptHost; import org.teavm.vm.spi.TeaVMHost; import org.teavm.vm.spi.TeaVMPlugin; diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java index d16904468..17a7e03d4 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java @@ -15,7 +15,7 @@ */ package org.teavm.jso.impl; -import org.teavm.javascript.target.TeaVMJavaScriptHost; +import org.teavm.javascript.backend.TeaVMJavaScriptHost; import org.teavm.vm.spi.TeaVMHost; import org.teavm.vm.spi.TeaVMPlugin; diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java index bbf83dbbe..0ef0823ba 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java @@ -16,7 +16,7 @@ package org.teavm.platform.plugin; import org.teavm.diagnostics.Diagnostics; -import org.teavm.javascript.target.TeaVMJavaScriptHost; +import org.teavm.javascript.backend.TeaVMJavaScriptHost; import org.teavm.model.*; import org.teavm.vm.spi.TeaVMHost; diff --git a/tests/src/test/java/org/teavm/dependency/ClassValueTest.java b/tests/src/test/java/org/teavm/dependency/ClassValueTest.java index c78af0db5..9a159e068 100644 --- a/tests/src/test/java/org/teavm/dependency/ClassValueTest.java +++ b/tests/src/test/java/org/teavm/dependency/ClassValueTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.apache.commons.io.output.ByteArrayOutputStream; import org.junit.Test; -import org.teavm.javascript.target.JavaScriptTarget; +import org.teavm.javascript.backend.JavaScriptTarget; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.tooling.TeaVMProblemRenderer; diff --git a/tests/src/test/java/org/teavm/tests/JSOTest.java b/tests/src/test/java/org/teavm/tests/JSOTest.java index 1a183edf3..cc5dbffdd 100644 --- a/tests/src/test/java/org/teavm/tests/JSOTest.java +++ b/tests/src/test/java/org/teavm/tests/JSOTest.java @@ -22,7 +22,7 @@ import java.io.ByteArrayOutputStream; import java.util.List; import org.junit.Test; import org.teavm.diagnostics.Problem; -import org.teavm.javascript.target.JavaScriptTarget; +import org.teavm.javascript.backend.JavaScriptTarget; import org.teavm.jso.JSBody; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; 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 e508e1ede..ee1e4afad 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -43,7 +43,7 @@ import org.teavm.debugging.information.DebugInformationBuilder; import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.ProblemProvider; import org.teavm.javascript.RenderingContext; -import org.teavm.javascript.target.JavaScriptTarget; +import org.teavm.javascript.backend.JavaScriptTarget; import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; 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 787b559eb..ed9ed92d6 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -47,7 +47,7 @@ import org.junit.runners.model.InitializationError; import org.teavm.callgraph.CallGraph; import org.teavm.diagnostics.DefaultProblemTextConsumer; import org.teavm.diagnostics.Problem; -import org.teavm.javascript.target.JavaScriptTarget; +import org.teavm.javascript.backend.JavaScriptTarget; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; import org.teavm.model.MethodDescriptor;