diff --git a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index 214faeb7d..3511ebfb8 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -656,6 +656,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } writer.append(";").softNewLine(); } + end = true; currentPart = 0; method.getBody().acceptVisitor(Renderer.this); diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/AstWriter.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/AstWriter.java index 910683fa0..0a9d85d0d 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/AstWriter.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/AstWriter.java @@ -95,7 +95,7 @@ public class AstWriter { private static final int PRECEDENCE_ASSIGN = 17; private static final int PRECEDENCE_COMMA = 18; private SourceWriter writer; - private Map nameMap = new HashMap<>(); + private Map nameMap = new HashMap<>(); private Set aliases = new HashSet<>(); public AstWriter(SourceWriter writer) { @@ -107,13 +107,13 @@ public class AstWriter { return; } if (aliases.add(name)) { - nameMap.put(name, name); + nameMap.put(name, () -> writer.append(name)); return; } for (int i = 0;; ++i) { String alias = name + "_" + i; if (aliases.add(alias)) { - nameMap.put(name, alias); + nameMap.put(name, () -> writer.append(alias)); return; } } @@ -123,7 +123,16 @@ public class AstWriter { if (!aliases.add(alias)) { throw new IllegalArgumentException("Alias " + alias + " is already occupied"); } - nameMap.put(name, alias); + nameMap.put(name, () -> writer.append(alias)); + } + + public void reserveName(String name) { + aliases.add(name); + nameMap.put(name, () -> writer.append(name)); + } + + public void declareNameEmitter(String name, NameEmitter emitter) { + nameMap.put(name, emitter); } public void hoist(AstNode node) { @@ -181,7 +190,11 @@ public class AstWriter { writer.append("false"); break; case Token.THIS: - writer.append(nameMap.containsKey("this") ? nameMap.get("this") : "this"); + if (nameMap.containsKey("this")) { + nameMap.get("this").emit(); + } else { + writer.append("this"); + } break; case Token.NULL: writer.append("null"); @@ -481,7 +494,7 @@ public class AstWriter { private void print(PropertyGet node) throws IOException { print(node.getLeft(), PRECEDENCE_MEMBER); writer.append('.'); - Map oldNameMap = nameMap; + Map oldNameMap = nameMap; nameMap = Collections.emptyMap(); print(node.getRight()); nameMap = oldNameMap; @@ -583,11 +596,11 @@ public class AstWriter { } private void print(Name node) throws IOException { - String alias = nameMap.get(node.getIdentifier()); + NameEmitter alias = nameMap.get(node.getIdentifier()); if (alias == null) { - alias = node.getIdentifier(); + alias = () -> writer.append(node.getIdentifier()); } - writer.append(alias); + alias.emit(); } private void print(RegExpLiteral node) throws IOException { @@ -618,7 +631,7 @@ public class AstWriter { } else if (node.isSetterMethod()) { writer.append("set "); } - Map oldNameMap = nameMap; + Map oldNameMap = nameMap; nameMap = Collections.emptyMap(); print(node.getLeft()); nameMap = oldNameMap; diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java new file mode 100644 index 000000000..c85531481 --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyAstEmitter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.plugin; + +import java.io.IOException; +import org.mozilla.javascript.ast.AstRoot; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.spi.GeneratorContext; +import org.teavm.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +class JSBodyAstEmitter implements JSBodyEmitter { + private boolean isStatic; + private AstRoot ast; + private String[] parameterNames; + + public JSBodyAstEmitter(boolean isStatic, AstRoot ast, String[] parameterNames) { + this.isStatic = isStatic; + this.ast = ast; + this.parameterNames = parameterNames; + } + + @Override + public void emit(InjectorContext context) throws IOException { + AstWriter astWriter = new AstWriter(context.getWriter()); + int paramIndex = 0; + if (!isStatic) { + int index = paramIndex++; + astWriter.declareNameEmitter("this", () -> context.writeExpr(context.getArgument(index))); + } + for (int i = 0; i < parameterNames.length; ++i) { + int index = paramIndex++; + astWriter.declareNameEmitter(parameterNames[i], () -> context.writeExpr(context.getArgument(index))); + } + astWriter.hoist(ast); + astWriter.print(ast); + context.getWriter().softNewLine(); + } + + @Override + public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + AstWriter astWriter = new AstWriter(writer); + int paramIndex = 1; + if (!isStatic) { + int index = paramIndex++; + astWriter.declareNameEmitter("this", () -> writer.append(context.getParameterName(index))); + } + for (int i = 0; i < parameterNames.length; ++i) { + int index = paramIndex++; + astWriter.declareNameEmitter(parameterNames[i], () -> writer.append(context.getParameterName(index))); + } + astWriter.hoist(ast); + astWriter.print(ast); + writer.softNewLine(); + } +} diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyBloatedEmitter.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyBloatedEmitter.java new file mode 100644 index 000000000..fdfc07b7d --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyBloatedEmitter.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.plugin; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.spi.GeneratorContext; +import org.teavm.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +class JSBodyBloatedEmitter implements JSBodyEmitter { + private boolean isStatic; + private MethodReference method; + private String script; + private String[] parameterNames; + + public JSBodyBloatedEmitter(boolean isStatic, MethodReference method, String script, String[] parameterNames) { + this.isStatic = isStatic; + this.method = method; + this.script = script; + this.parameterNames = parameterNames; + } + + @Override + public void emit(InjectorContext context) throws IOException { + emit(context.getWriter(), index -> context.writeExpr(context.getArgument(index))); + } + + @Override + public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + emit(writer, index -> writer.append(context.getParameterName(index + 1))); + } + + private void emit(SourceWriter writer, EmissionStrategy strategy) throws IOException { + int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1; + + writer.append("if (!").appendMethodBody(method).append(".$native)").ws().append('{').indent().newLine(); + writer.appendMethodBody(method).append(".$native").ws().append('=').ws().append("function("); + int count = method.parameterCount(); + for (int i = 0; i < count; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + writer.append('_').append(i); + } + writer.append(')').ws().append('{').softNewLine().indent(); + + writer.append("return (function("); + for (int i = 0; i < bodyParamCount; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + String name = parameterNames[i]; + writer.append(name); + } + writer.append(')').ws().append('{').softNewLine().indent(); + writer.append(script).softNewLine(); + writer.outdent().append("})"); + if (!isStatic) { + writer.append(".call"); + } + writer.append('('); + for (int i = 0; i < count; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + writer.append('_').append(i); + } + writer.append(");").softNewLine(); + writer.outdent().append("};").softNewLine(); + writer.appendMethodBody(method).ws().append('=').ws().appendMethodBody(method).append(".$native;") + .softNewLine(); + writer.outdent().append("}").softNewLine(); + + writer.append("return ").appendMethodBody(method).append('('); + for (int i = 0; i < count; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + strategy.emitArgument(i); + } + writer.append(");").softNewLine(); + } + + interface EmissionStrategy { + void emitArgument(int argument) throws IOException; + } +} diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyEmitter.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyEmitter.java new file mode 100644 index 000000000..f793f765e --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyEmitter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.plugin; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.spi.GeneratorContext; +import org.teavm.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +interface JSBodyEmitter { + void emit(InjectorContext context) throws IOException; + + void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException; +} diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java index d760efdad..f97b4f0ee 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java @@ -16,113 +16,29 @@ package org.teavm.jso.plugin; import java.io.IOException; -import java.io.StringReader; -import java.util.List; -import org.mozilla.javascript.CompilerEnvirons; -import org.mozilla.javascript.Context; -import org.mozilla.javascript.ast.AstRoot; import org.teavm.codegen.SourceWriter; import org.teavm.javascript.spi.Generator; import org.teavm.javascript.spi.GeneratorContext; -import org.teavm.model.AnnotationReader; -import org.teavm.model.AnnotationValue; -import org.teavm.model.CallLocation; -import org.teavm.model.ClassReader; -import org.teavm.model.MethodReader; +import org.teavm.javascript.spi.Injector; +import org.teavm.javascript.spi.InjectorContext; import org.teavm.model.MethodReference; /** * * @author Alexey Andreev */ -public class JSBodyGenerator implements Generator { +public class JSBodyGenerator implements Injector, Generator { + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + JSBodyRepository emitterRepository = context.getService(JSBodyRepository.class); + JSBodyEmitter emitter = emitterRepository.emitters.get(methodRef); + emitter.emit(context); + } + @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { - ClassReader cls = context.getClassSource().get(methodRef.getClassName()); - MethodReader method = cls.getMethod(methodRef.getDescriptor()); - AnnotationReader annot = method.getAnnotations().get(JSBodyImpl.class.getName()); - boolean isStatic = annot.getValue("isStatic").getBoolean(); - List paramNames = annot.getValue("params").getList(); - String script = annot.getValue("script").getString(); - - TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(context.getDiagnostics(), - new CallLocation(methodRef)); - CompilerEnvirons env = new CompilerEnvirons(); - env.setRecoverFromErrors(true); - env.setLanguageVersion(Context.VERSION_1_8); - env.setIdeMode(true); - JSParser parser = new JSParser(env, errorReporter); - parser.enterFunction(); - AstRoot rootNode = parser.parse(new StringReader(script), null, 0); - parser.exitFunction(); - if (errorReporter.hasErrors()) { - generateBloated(context, writer, methodRef, method, isStatic, paramNames, script); - return; - } - - AstWriter astWriter = new AstWriter(writer); - int paramIndex = 1; - if (!isStatic) { - astWriter.declareAlias("this", context.getParameterName(paramIndex++)); - } - for (int i = 0; i < paramNames.size(); ++i) { - astWriter.declareAlias(paramNames.get(i).getString(), context.getParameterName(paramIndex++)); - } - astWriter.hoist(rootNode); - astWriter.print(rootNode); - writer.softNewLine(); - } - - private void generateBloated(GeneratorContext context, SourceWriter writer, MethodReference methodRef, - MethodReader method, boolean isStatic, List paramNames, String script) - throws IOException { - int bodyParamCount = isStatic ? method.parameterCount() : method.parameterCount() - 1; - - writer.append("if (!").appendMethodBody(methodRef).append(".$native)").ws().append('{').indent().newLine(); - writer.appendMethodBody(methodRef).append(".$native").ws().append('=').ws().append("function("); - int count = method.parameterCount(); - for (int i = 0; i < count; ++i) { - if (i > 0) { - writer.append(',').ws(); - } - writer.append('_').append(context.getParameterName(i + 1)); - } - writer.append(')').ws().append('{').softNewLine().indent(); - - writer.append("return (function("); - for (int i = 0; i < bodyParamCount; ++i) { - if (i > 0) { - writer.append(',').ws(); - } - String name = paramNames.get(i).getString(); - writer.append(name); - } - writer.append(')').ws().append('{').softNewLine().indent(); - writer.append(script).softNewLine(); - writer.outdent().append("})"); - if (!isStatic) { - writer.append(".call"); - } - writer.append('('); - for (int i = 0; i < count; ++i) { - if (i > 0) { - writer.append(',').ws(); - } - writer.append('_').append(context.getParameterName(i + 1)); - } - writer.append(");").softNewLine(); - writer.outdent().append("};").softNewLine(); - writer.appendMethodBody(methodRef).ws().append('=').ws().appendMethodBody(methodRef).append(".$native;") - .softNewLine(); - writer.outdent().append("}").softNewLine(); - - writer.append("return ").appendMethodBody(methodRef).append('('); - for (int i = 0; i < count; ++i) { - if (i > 0) { - writer.append(',').ws(); - } - writer.append(context.getParameterName(i + 1)); - } - writer.append(");").softNewLine(); + JSBodyRepository emitterRepository = context.getService(JSBodyRepository.class); + JSBodyEmitter emitter = emitterRepository.emitters.get(methodRef); + emitter.emit(context, writer, methodRef); } } diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRef.java similarity index 90% rename from teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java rename to teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRef.java index 3cc28323a..6138a2313 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRef.java @@ -26,10 +26,6 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@interface JSBodyImpl { - String[] params(); - - String script(); - - boolean isStatic(); +@interface JSBodyRef { + String method(); } diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java new file mode 100644 index 000000000..bc24b1114 --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSBodyRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.plugin; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +class JSBodyRepository { + public final Map emitters = new HashMap<>(); + public final Map methodMap = new HashMap<>(); + public final Set processedMethods = new HashSet<>(); +} diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSOPlugin.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSOPlugin.java index 9bd7a16cd..c23a98465 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSOPlugin.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSOPlugin.java @@ -25,7 +25,9 @@ import org.teavm.vm.spi.TeaVMPlugin; public class JSOPlugin implements TeaVMPlugin { @Override public void install(TeaVMHost host) { - host.add(new JSObjectClassTransformer()); + JSBodyRepository repository = new JSBodyRepository(); + host.registerService(JSBodyRepository.class, repository); + host.add(new JSObjectClassTransformer(repository)); JSODependencyListener dependencyListener = new JSODependencyListener(); JSOAliasRenderer aliasRenderer = new JSOAliasRenderer(dependencyListener); host.add(dependencyListener); diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java index bb7c92be1..b6ba5e1a2 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java @@ -16,8 +16,11 @@ package org.teavm.jso.plugin; import org.teavm.diagnostics.Diagnostics; -import org.teavm.jso.JSBody; -import org.teavm.model.*; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; /** * @@ -25,6 +28,11 @@ import org.teavm.model.*; */ public class JSObjectClassTransformer implements ClassHolderTransformer { private JavascriptNativeProcessor processor; + private JSBodyRepository repository; + + public JSObjectClassTransformer(JSBodyRepository repository) { + this.repository = repository; + } @Override public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { @@ -46,12 +54,10 @@ public class JSObjectClassTransformer implements ClassHolderTransformer { } } for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { - if (method.getAnnotations().get(JSBody.class.getName()) != null) { - processor.processJSBody(cls, method); - } else if (method.getProgram() != null - && method.getAnnotations().get(JSBodyImpl.class.getName()) == null) { - processor.processProgram(method); + if (method.getProgram() != null) { + processor.processProgram(repository, method); } } + processor.createJSMethods(repository, cls); } } diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java index f2e062eb4..a8fc3233b 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java @@ -15,6 +15,8 @@ */ package org.teavm.jso.plugin; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -23,6 +25,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; +import org.mozilla.javascript.CompilerEnvirons; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ast.AstRoot; import org.teavm.diagnostics.Diagnostics; import org.teavm.javascript.spi.GeneratedBy; import org.teavm.javascript.spi.Sync; @@ -60,7 +65,6 @@ import org.teavm.model.Variable; import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.CastInstruction; import org.teavm.model.instructions.ClassConstantInstruction; -import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.StringConstantInstruction; @@ -275,7 +279,7 @@ class JavascriptNativeProcessor { return staticSignature; } - public void processProgram(MethodHolder methodToProcess) { + public void processProgram(JSBodyRepository repository, MethodHolder methodToProcess) { program = methodToProcess.getProgram(); for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlock block = program.basicBlockAt(i); @@ -286,146 +290,218 @@ class JavascriptNativeProcessor { continue; } InvokeInstruction invoke = (InvokeInstruction) insn; - if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) { - continue; - } - replacement.clear(); MethodReader method = getMethod(invoke.getMethod()); - if (method == null || method.hasModifier(ElementModifier.STATIC)) { + if (method == null) { continue; } - - if (method.hasModifier(ElementModifier.FINAL)) { - MethodReader overriden = getOverridenMethod(method); - if (overriden != null) { - CallLocation callLocation = new CallLocation(methodToProcess.getReference(), - insn.getLocation()); - diagnostics.error(callLocation, "JS final method {{m0}} overrides {{M1}}. " - + "Overriding final method of overlay types is prohibited.", - method.getReference(), overriden.getReference()); - } - if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) { - invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static", - getStaticSignature(method.getReference()))); - invoke.getArguments().add(0, invoke.getInstance()); - invoke.setInstance(null); - } - invoke.setType(InvocationType.SPECIAL); - continue; - } - CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); - if (method.getAnnotations().get(JSProperty.class.getName()) != null) { - if (isProperGetter(method.getDescriptor())) { - String propertyName; - AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName()); - if (annot.getValue("value") != null) { - propertyName = annot.getValue("value").getString(); - } else { - propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2) - : cutPrefix(method.getName(), 3); - } - Variable result = invoke.getReceiver() != null ? program.createVariable() : null; - addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation()); - if (result != null) { - result = unwrap(callLocation, result, method.getResultType()); - copyVar(result, invoke.getReceiver(), invoke.getLocation()); - } - } else if (isProperSetter(method.getDescriptor())) { - String propertyName; - AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName()); - if (annot.getValue("value") != null) { - propertyName = annot.getValue("value").getString(); - } else { - propertyName = cutPrefix(method.getName(), 3); - } - Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0), - method.parameterType(0)); - addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation()); - } else { - diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property " - + "declaration", invoke.getMethod()); - continue; - } - } else if (method.getAnnotations().get(JSIndexer.class.getName()) != null) { - if (isProperGetIndexer(method.getDescriptor())) { - Variable result = invoke.getReceiver() != null ? program.createVariable() : null; - addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0), - method.parameterType(0), invoke.getLocation()), result, invoke.getLocation()); - if (result != null) { - result = unwrap(callLocation, result, method.getResultType()); - copyVar(result, invoke.getReceiver(), invoke.getLocation()); - } - } else if (isProperSetIndexer(method.getDescriptor())) { - Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0), - invoke.getLocation()); - Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1), - invoke.getLocation()); - addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation()); - } else { - diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer " - + "declaration", invoke.getMethod()); - continue; - } - } else { - String name = method.getName(); - - AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName()); - if (methodAnnot != null) { - AnnotationValue redefinedMethodName = methodAnnot.getValue("value"); - if (redefinedMethodName != null) { - name = redefinedMethodName.getString(); - } - } - if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) { - diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method " - + "declaration", invoke.getMethod()); - continue; - } - - for (ValueType arg : method.getParameterTypes()) { - if (!isSupportedType(arg)) { - diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method " - + "or constructor declaration", invoke.getMethod()); - continue; - } - } - Variable result = invoke.getReceiver() != null ? program.createVariable() : null; - InvokeInstruction newInvoke = new InvokeInstruction(); - ValueType[] signature = new ValueType[method.parameterCount() + 3]; - Arrays.fill(signature, ValueType.object(JSObject.class.getName())); - newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature)); - newInvoke.setType(InvocationType.SPECIAL); - newInvoke.setReceiver(result); - newInvoke.getArguments().add(invoke.getInstance()); - newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()), - invoke.getLocation())); - newInvoke.setLocation(invoke.getLocation()); - for (int k = 0; k < invoke.getArguments().size(); ++k) { - Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k), - method.parameterType(k)); - newInvoke.getArguments().add(arg); - } - replacement.add(newInvoke); - if (result != null) { - result = unwrap(callLocation, result, method.getResultType()); - copyVar(result, invoke.getReceiver(), invoke.getLocation()); - } + replacement.clear(); + if (processInvocation(repository, method, callLocation, invoke)) { + block.getInstructions().set(j, replacement.get(0)); + block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size())); + j += replacement.size() - 1; } - block.getInstructions().set(j, replacement.get(0)); - block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size())); - j += replacement.size() - 1; } } } - public void processJSBody(ClassHolder cls, MethodHolder methodToProcess) { + private boolean processInvocation(JSBodyRepository repository, MethodReader method, CallLocation callLocation, + InvokeInstruction invoke) { + if (method.getAnnotations().get(JSBody.class.getName()) != null) { + return processJSBodyInvocation(repository, method, callLocation, invoke); + } + + if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) { + return false; + } + + if (method == null || method.hasModifier(ElementModifier.STATIC)) { + return false; + } + + if (method.hasModifier(ElementModifier.FINAL)) { + MethodReader overriden = getOverridenMethod(method); + if (overriden != null) { + diagnostics.error(callLocation, "JS final method {{m0}} overrides {{M1}}. " + + "Overriding final method of overlay types is prohibited.", + method.getReference(), overriden.getReference()); + } + if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) { + invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static", + getStaticSignature(method.getReference()))); + invoke.getArguments().add(0, invoke.getInstance()); + invoke.setInstance(null); + } + invoke.setType(InvocationType.SPECIAL); + return false; + } + + if (method.getAnnotations().get(JSProperty.class.getName()) != null) { + return processProperty(method, callLocation, invoke); + } else if (method.getAnnotations().get(JSIndexer.class.getName()) != null) { + return processIndexer(method, callLocation, invoke); + } else { + return processMethod(method, callLocation, invoke); + } + } + + private boolean processJSBodyInvocation(JSBodyRepository repository, MethodReader method, + CallLocation callLocation, InvokeInstruction invoke) { + requireJSBody(repository, diagnostics, method); + MethodReference delegate = repository.methodMap.get(method.getReference()); + if (delegate == null) { + return false; + } + + Variable result = invoke.getReceiver() != null ? program.createVariable() : null; + InvokeInstruction newInvoke = new InvokeInstruction(); + ValueType[] signature = new ValueType[method.parameterCount() + 3]; + Arrays.fill(signature, ValueType.object(JSObject.class.getName())); + newInvoke.setMethod(delegate); + newInvoke.setType(InvocationType.SPECIAL); + newInvoke.setReceiver(result); + newInvoke.setLocation(invoke.getLocation()); + if (invoke.getInstance() != null) { + Variable arg = wrapArgument(callLocation, invoke.getInstance(), ValueType.object(method.getOwnerName())); + newInvoke.getArguments().add(arg); + } + for (int k = 0; k < invoke.getArguments().size(); ++k) { + Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k), + method.parameterType(k)); + newInvoke.getArguments().add(arg); + } + replacement.add(newInvoke); + if (result != null) { + result = unwrap(callLocation, result, method.getResultType()); + copyVar(result, invoke.getReceiver(), invoke.getLocation()); + } + + return true; + } + + private boolean processProperty(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { + if (isProperGetter(method.getDescriptor())) { + String propertyName; + AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName()); + if (annot.getValue("value") != null) { + propertyName = annot.getValue("value").getString(); + } else { + propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2) + : cutPrefix(method.getName(), 3); + } + Variable result = invoke.getReceiver() != null ? program.createVariable() : null; + addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation()); + if (result != null) { + result = unwrap(callLocation, result, method.getResultType()); + copyVar(result, invoke.getReceiver(), invoke.getLocation()); + } + return true; + } + if (isProperSetter(method.getDescriptor())) { + String propertyName; + AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName()); + if (annot.getValue("value") != null) { + propertyName = annot.getValue("value").getString(); + } else { + propertyName = cutPrefix(method.getName(), 3); + } + Variable wrapped = wrapArgument(callLocation, invoke.getArguments().get(0), + method.parameterType(0)); + addPropertySet(propertyName, invoke.getInstance(), wrapped, invoke.getLocation()); + return true; + } + diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property " + + "declaration", invoke.getMethod()); + return false; + } + + private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { + if (isProperGetIndexer(method.getDescriptor())) { + Variable result = invoke.getReceiver() != null ? program.createVariable() : null; + addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0), + method.parameterType(0), invoke.getLocation()), result, invoke.getLocation()); + if (result != null) { + result = unwrap(callLocation, result, method.getResultType()); + copyVar(result, invoke.getReceiver(), invoke.getLocation()); + } + return true; + } + if (isProperSetIndexer(method.getDescriptor())) { + Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0), + invoke.getLocation()); + Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1), + invoke.getLocation()); + addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation()); + return true; + } + diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer " + + "declaration", invoke.getMethod()); + return false; + } + + private boolean processMethod(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { + String name = method.getName(); + + AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName()); + if (methodAnnot != null) { + AnnotationValue redefinedMethodName = methodAnnot.getValue("value"); + if (redefinedMethodName != null) { + name = redefinedMethodName.getString(); + } + } + if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) { + diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method " + + "declaration", invoke.getMethod()); + return false; + } + + for (ValueType arg : method.getParameterTypes()) { + if (!isSupportedType(arg)) { + diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method " + + "or constructor declaration", invoke.getMethod()); + return false; + } + } + + Variable result = invoke.getReceiver() != null ? program.createVariable() : null; + InvokeInstruction newInvoke = new InvokeInstruction(); + ValueType[] signature = new ValueType[method.parameterCount() + 3]; + Arrays.fill(signature, ValueType.object(JSObject.class.getName())); + newInvoke.setMethod(new MethodReference(JS.class.getName(), "invoke", signature)); + newInvoke.setType(InvocationType.SPECIAL); + newInvoke.setReceiver(result); + newInvoke.getArguments().add(invoke.getInstance()); + newInvoke.getArguments().add(addStringWrap(addString(name, invoke.getLocation()), + invoke.getLocation())); + newInvoke.setLocation(invoke.getLocation()); + for (int k = 0; k < invoke.getArguments().size(); ++k) { + Variable arg = wrapArgument(callLocation, invoke.getArguments().get(k), + method.parameterType(k)); + newInvoke.getArguments().add(arg); + } + replacement.add(newInvoke); + if (result != null) { + result = unwrap(callLocation, result, method.getResultType()); + copyVar(result, invoke.getReceiver(), invoke.getLocation()); + } + + return true; + } + + private void requireJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) { + if (!repository.processedMethods.add(methodToProcess.getReference())) { + return; + } + processJSBody(repository, diagnostics, methodToProcess); + } + + private void processJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) { CallLocation location = new CallLocation(methodToProcess.getReference()); boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC); // validate parameter names - AnnotationHolder bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName()); + AnnotationReader bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName()); int jsParamCount = bodyAnnot.getValue("params").getList().size(); if (methodToProcess.parameterCount() != jsParamCount) { diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount() @@ -433,10 +509,6 @@ class JavascriptNativeProcessor { return; } - // remove annotation and make non-native - methodToProcess.getAnnotations().remove(JSBody.class.getName()); - methodToProcess.getModifiers().remove(ElementModifier.NATIVE); - // generate parameter types for original method and validate int paramCount = methodToProcess.parameterCount(); if (!isStatic) { @@ -445,11 +517,11 @@ class JavascriptNativeProcessor { ValueType[] paramTypes = new ValueType[paramCount]; int offset = 0; if (!isStatic) { - ValueType paramType = ValueType.object(cls.getName()); + ValueType paramType = ValueType.object(methodToProcess.getOwnerName()); paramTypes[offset++] = paramType; if (!isSupportedType(paramType)) { diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}", - methodToProcess.getReference(), cls.getName()); + methodToProcess.getReference(), methodToProcess.getOwnerName()); } } if (methodToProcess.getResultType() != ValueType.VOID && !isSupportedType(methodToProcess.getResultType())) { @@ -470,52 +542,58 @@ class JavascriptNativeProcessor { : ValueType.parse(JSObject.class); // create proxy method - MethodHolder proxyMethod = new MethodHolder("$js_body$_" + methodIndexGenerator++, proxyParamTypes); - proxyMethod.getModifiers().add(ElementModifier.NATIVE); - proxyMethod.getModifiers().add(ElementModifier.STATIC); - AnnotationHolder genBodyAnnot = new AnnotationHolder(JSBodyImpl.class.getName()); - genBodyAnnot.getValues().put("script", bodyAnnot.getValue("script")); - genBodyAnnot.getValues().put("params", bodyAnnot.getValue("params")); - genBodyAnnot.getValues().put("isStatic", new AnnotationValue(isStatic)); - AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName()); - generatorAnnot.getValues().put("value", new AnnotationValue(ValueType.parse(JSBodyGenerator.class))); - proxyMethod.getAnnotations().add(genBodyAnnot); - proxyMethod.getAnnotations().add(generatorAnnot); - cls.addMethod(proxyMethod); + MethodReference proxyMethod = new MethodReference(methodToProcess.getOwnerName(), + methodToProcess.getName() + "$js_body$_" + methodIndexGenerator++, proxyParamTypes); + String script = bodyAnnot.getValue("script").getString(); + String[] parameterNames = bodyAnnot.getValue("params").getList().stream() + .map(ann -> ann.getString()) + .toArray(sz -> new String[sz]); - // create program that invokes proxy method - program = new Program(); - BasicBlock block = program.createBasicBlock(); - for (int i = 0; i < paramCount; ++i) { - program.createVariable(); + // Parse JS script + TeaVMErrorReporter errorReporter = new TeaVMErrorReporter(diagnostics, + new CallLocation(methodToProcess.getReference())); + CompilerEnvirons env = new CompilerEnvirons(); + env.setRecoverFromErrors(true); + env.setLanguageVersion(Context.VERSION_1_8); + env.setIdeMode(true); + JSParser parser = new JSParser(env, errorReporter); + parser.enterFunction(); + AstRoot rootNode; + try { + rootNode = parser.parse(new StringReader(script), null, 0); + } catch (IOException e) { + throw new RuntimeException("IO Error occured", e); } - if (isStatic) { - program.createVariable(); - } - methodToProcess.setProgram(program); + parser.exitFunction(); - // Generate invoke instruction - replacement.clear(); - InvokeInstruction invoke = new InvokeInstruction(); - invoke.setType(InvocationType.SPECIAL); - invoke.setMethod(proxyMethod.getReference()); - for (int i = 0; i < paramCount; ++i) { - Variable var = program.variableAt(isStatic ? i + 1 : i); - invoke.getArguments().add(wrapArgument(location, var, paramTypes[i])); + if (errorReporter.hasErrors()) { + repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod, + script, parameterNames)); + } else { + repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, rootNode, + parameterNames)); } - block.getInstructions().addAll(replacement); - block.getInstructions().add(invoke); + repository.methodMap.put(methodToProcess.getReference(), proxyMethod); + } - // Generate return - ExitInstruction exit = new ExitInstruction(); - if (methodToProcess.getResultType() != ValueType.VOID) { - replacement.clear(); - Variable result = program.createVariable(); - invoke.setReceiver(result); - exit.setValueToReturn(unwrap(location, result, methodToProcess.getResultType())); - block.getInstructions().addAll(replacement); + public void createJSMethods(JSBodyRepository repository, ClassHolder cls) { + for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { + MethodReference methodRef = method.getReference(); + if (method.getAnnotations().get(JSBody.class.getName()) != null) { + requireJSBody(repository, diagnostics, method); + if (repository.methodMap.containsKey(method.getReference())) { + MethodReference proxyRef = repository.methodMap.get(methodRef); + MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor()); + proxyMethod.getModifiers().add(ElementModifier.NATIVE); + proxyMethod.getModifiers().add(ElementModifier.STATIC); + AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName()); + generatorAnnot.getValues().put("value", + new AnnotationValue(ValueType.parse(JSBodyGenerator.class))); + proxyMethod.getAnnotations().add(generatorAnnot); + cls.addMethod(proxyMethod); + } + } } - block.getInstructions().add(exit); } private void addPropertyGet(String propertyName, Variable instance, Variable receiver, @@ -971,6 +1049,9 @@ class JavascriptNativeProcessor { private MethodReader getMethod(MethodReference ref) { ClassReader cls = classSource.get(ref.getClassName()); + if (cls == null) { + return null; + } MethodReader method = cls.getMethod(ref.getDescriptor()); if (method != null) { return method; diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/NameEmitter.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/NameEmitter.java new file mode 100644 index 000000000..8e5206a89 --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/NameEmitter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.plugin; + +import java.io.IOException; + +/** + * + * @author Alexey Andreev + */ +interface NameEmitter { + void emit() throws IOException; +}