From bae388a7782a4e981631732a2916518691b395ea Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 25 Sep 2015 22:56:41 +0300 Subject: [PATCH] First version of Java callbacks, without dependency checker hook and parameter conversion. --- .../org/teavm/model/MethodDescriptor.java | 34 +++- .../java/org/teavm/model/MethodReference.java | 14 +- .../main/java/org/teavm/model/ValueType.java | 38 ++++- .../java/org/teavm/jso/plugin/AstWriter.java | 102 ++++++------ .../teavm/jso/plugin/JSBodyAstEmitter.java | 4 +- .../jso/plugin/JavaInvocationValidator.java | 155 ++++++++++++++++++ .../jso/plugin/JavascriptNativeProcessor.java | 4 +- .../org/teavm/jso/plugin/AstWriterTest.java | 2 +- .../teavm/jso/test/JavaInvocationTest.java | 39 +++++ 9 files changed, 324 insertions(+), 68 deletions(-) create mode 100644 teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavaInvocationValidator.java create mode 100644 teavm-jso/src/test/java/org/teavm/jso/test/JavaInvocationTest.java diff --git a/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java b/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java index 2b646d060..c185888e7 100644 --- a/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java +++ b/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java @@ -96,24 +96,46 @@ public class MethodDescriptor { } public static MethodDescriptor parse(String text) { - int parenIndex = text.indexOf('('); - if (parenIndex < 0) { + MethodDescriptor desc = parseIfPossible(text); + if (desc == null) { throw new IllegalArgumentException("Wrong method descriptor: " + text); } + return desc; + } + + public static MethodDescriptor parseIfPossible(String text) { + int parenIndex = text.indexOf('('); + if (parenIndex < 1) { + return null; + } return new MethodDescriptor(text.substring(0, parenIndex), parseSignature(text.substring(parenIndex))); } public static ValueType[] parseSignature(String text) { + ValueType[] signature = parseSignatureIfPossible(text); + if (signature == null) { + throw new IllegalArgumentException("Illegal method signature: " + text); + } + return signature; + } + + public static ValueType[] parseSignatureIfPossible(String text) { if (text.charAt(0) != '(') { - throw new IllegalArgumentException("Wrong method descriptor: " + text); + return null; } int index = text.indexOf(')', 1); - if (index == -1) { - throw new IllegalArgumentException("Wrong method descriptor: " + text); + if (index < 0) { + return null; + } + ValueType[] params = ValueType.parseManyIfPossible(text.substring(1, index)); + if (params == null) { + return null; } - ValueType[] params = ValueType.parseMany(text.substring(1, index)); ValueType result = ValueType.parse(text.substring(index + 1)); + if (result == null) { + return null; + } ValueType[] signature = new ValueType[params.length + 1]; System.arraycopy(params, 0, signature, 0, params.length); signature[params.length] = result; diff --git a/teavm-core/src/main/java/org/teavm/model/MethodReference.java b/teavm-core/src/main/java/org/teavm/model/MethodReference.java index 42123a8c5..a60d8b4ea 100644 --- a/teavm-core/src/main/java/org/teavm/model/MethodReference.java +++ b/teavm-core/src/main/java/org/teavm/model/MethodReference.java @@ -140,9 +140,21 @@ public class MethodReference { } public static MethodReference parse(String string) { + MethodReference reference = parseIfPossible(string); + if (reference == null) { + throw new IllegalArgumentException("Illegal method reference: " + string); + } + return reference; + } + + public static MethodReference parseIfPossible(String string) { int index = string.lastIndexOf('.'); + if (index < 1) { + return null; + } String className = string.substring(0, index); - return new MethodReference(className, MethodDescriptor.parse(string.substring(index + 1))); + MethodDescriptor desc = MethodDescriptor.parseIfPossible(string.substring(index + 1)); + return desc != null ? new MethodReference(className, desc) : null; } public String signatureToString() { diff --git a/teavm-core/src/main/java/org/teavm/model/ValueType.java b/teavm-core/src/main/java/org/teavm/model/ValueType.java index 5e1dc9982..4ca238188 100644 --- a/teavm-core/src/main/java/org/teavm/model/ValueType.java +++ b/teavm-core/src/main/java/org/teavm/model/ValueType.java @@ -212,11 +212,23 @@ public abstract class ValueType { } public static ValueType[] parseMany(String text) { + ValueType[] types = parseManyIfPossible(text); + if (types == null) { + throw new IllegalArgumentException("Illegal method type signature: " + text); + } + return types; + } + + public static ValueType[] parseManyIfPossible(String text) { List types = new ArrayList<>(); int index = 0; while (index < text.length()) { int nextIndex = cut(text, index); - types.add(parse(text.substring(index, nextIndex))); + ValueType type = parse(text.substring(index, nextIndex)); + if (type == null) { + return null; + } + types.add(type); index = nextIndex; } return types.toArray(new ValueType[types.size()]); @@ -224,18 +236,30 @@ public abstract class ValueType { private static int cut(String text, int index) { while (text.charAt(index) == '[') { - ++index; + if (++index >= text.length()) { + return index; + } } if (text.charAt(index) != 'L') { return index + 1; } while (text.charAt(index) != ';') { - ++index; + if (++index >= text.length()) { + return index; + } } return index + 1; } public static ValueType parse(String string) { + ValueType type = parseIfPossible(string); + if (type == null) { + throw new IllegalArgumentException("Illegal type signature: " + string); + } + return type; + } + + public static ValueType parseIfPossible(String string) { int arrayDegree = 0; int left = 0; while (string.charAt(left) == '[') { @@ -243,7 +267,13 @@ public abstract class ValueType { ++left; } string = string.substring(left); + if (string.isEmpty()) { + return null; + } ValueType type = parseImpl(string); + if (type == null) { + return null; + } while (arrayDegree-- > 0) { type = arrayOf(type); } @@ -295,7 +325,7 @@ public abstract class ValueType { } return object(string.substring(1, string.length() - 1).replace('/', '.')); default: - throw new IllegalArgumentException(); + return null; } } 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 ed73c5b30..6f53eea8a 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 @@ -71,8 +71,6 @@ import org.mozilla.javascript.ast.VariableDeclaration; import org.mozilla.javascript.ast.VariableInitializer; import org.mozilla.javascript.ast.WhileLoop; import org.teavm.codegen.SourceWriter; -import org.teavm.diagnostics.Diagnostics; -import org.teavm.model.CallLocation; import org.teavm.model.MethodReference; /** @@ -97,13 +95,11 @@ public class AstWriter { private static final int PRECEDENCE_COND = 16; private static final int PRECEDENCE_ASSIGN = 17; private static final int PRECEDENCE_COMMA = 18; - private Diagnostics diagnostics; private SourceWriter writer; private Map nameMap = new HashMap<>(); private Set aliases = new HashSet<>(); - public AstWriter(Diagnostics diagnostics, SourceWriter writer) { - this.diagnostics = diagnostics; + public AstWriter(SourceWriter writer) { this.writer = writer; } @@ -506,12 +502,8 @@ public class AstWriter { } private void print(FunctionCall node, int precedence) throws IOException { - if (node.getTarget() instanceof PropertyGet) { - PropertyGet propertyGet = (PropertyGet) node.getTarget(); - MethodReference methodRef = getJavaMethodSelector(propertyGet.getTarget()); - if (methodRef != null && propertyGet.getProperty().getIdentifier().equals("invoke")) { - return; - } + if (tryJavaInvocation(node)) { + return; } if (precedence < PRECEDENCE_FUNCTION) { @@ -537,55 +529,59 @@ public class AstWriter { } } - private MethodReference getJavaMethodSelector(AstNode node) { - if (!(node instanceof FunctionCall)) { - return null; - } - FunctionCall call = (FunctionCall) node; - if (!isJavaMethodRepository(call.getTarget())) { - return null; - } - if (call.getArguments().size() != 1) { - diagnostics.warning(new CallLocation(null), "JavaMethods.get method should take exactly one argument"); - return null; - } - StringBuilder nameBuilder = new StringBuilder(); - if (!extractMethodName(call.getArguments().get(0), nameBuilder)) { - diagnostics.warning(new CallLocation(null), "JavaMethods.get method should take string constant"); - return null; - } - return MethodReference.parse(nameBuilder.toString()); - } - - private boolean isJavaMethodRepository(AstNode node) { - if (!(node instanceof PropertyGet)) { - return false; - } - PropertyGet propertyGet = (PropertyGet) node; - - if (propertyGet.getLeft() instanceof Name) { - return false; - } - if (!((Name) propertyGet.getTarget()).getIdentifier().equals("JavaMethods")) { - return false; - } - if (!propertyGet.getProperty().getIdentifier().equals("get")) { + private boolean tryJavaInvocation(FunctionCall node) throws IOException { + if (!(node.getTarget() instanceof PropertyGet)) { return false; } - return true; - } + PropertyGet propertyGet = (PropertyGet) node.getTarget(); + String callMethod = getJavaMethod(propertyGet.getTarget()); + if (callMethod == null || !propertyGet.getProperty().getIdentifier().equals("invoke")) { + return false; + } - private boolean extractMethodName(AstNode node, StringBuilder sb) { - if (node.getType() == Token.ADD) { - InfixExpression infix = (InfixExpression) node; - return extractMethodName(infix.getLeft(), sb) && extractMethodName(infix.getRight(), sb); - } else if (node.getType() == Token.STRING) { - sb.append(((StringLiteral) node).getValue()); - return true; + boolean isStatic; + if (callMethod.startsWith("S")) { + isStatic = true; + } else if (callMethod.startsWith("V")) { + isStatic = false; } else { return false; } + callMethod = callMethod.substring(1); + MethodReference method = MethodReference.parseIfPossible(callMethod); + if (method == null) { + return false; + } + + if (isStatic) { + writer.appendMethodBody(method).append('('); + printList(node.getArguments()); + writer.append(')'); + } else { + print(node.getArguments().get(0)); + writer.append('.').appendMethod(method.getDescriptor()).append('('); + if (node.getArguments().size() > 1) { + print(node.getArguments().get(1)); + } + for (int i = 2; i < node.getArguments().size(); ++i) { + writer.append(',').ws(); + print(node.getArguments().get(i)); + } + writer.append(')'); + } + return true; + } + + private String getJavaMethod(AstNode node) { + if (!(node instanceof StringLiteral)) { + return null; + } + String str = ((StringLiteral) node).getValue(); + if (!str.startsWith("$$JSO$$_")) { + return null; + } + return str.substring("$$JSO$$_".length()); } private void print(ConditionalExpression node, int precedence) throws IOException { 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 index 7d54d9d64..0683f027d 100644 --- 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 @@ -39,7 +39,7 @@ class JSBodyAstEmitter implements JSBodyEmitter { @Override public void emit(InjectorContext context) throws IOException { - AstWriter astWriter = new AstWriter(null, context.getWriter()); + AstWriter astWriter = new AstWriter(context.getWriter()); int paramIndex = 0; if (!isStatic) { int index = paramIndex++; @@ -55,7 +55,7 @@ class JSBodyAstEmitter implements JSBodyEmitter { @Override public void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { - AstWriter astWriter = new AstWriter(context.getDiagnostics(), writer); + AstWriter astWriter = new AstWriter(writer); int paramIndex = 1; if (!isStatic) { int index = paramIndex++; diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavaInvocationValidator.java b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavaInvocationValidator.java new file mode 100644 index 000000000..a0ab73e96 --- /dev/null +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/plugin/JavaInvocationValidator.java @@ -0,0 +1,155 @@ +/* + * 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 org.mozilla.javascript.Token; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.InfixExpression; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.NodeVisitor; +import org.mozilla.javascript.ast.PropertyGet; +import org.mozilla.javascript.ast.StringLiteral; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +class JavaInvocationValidator implements NodeVisitor { + private ClassReaderSource classSource; + private Diagnostics diagnostics; + private CallLocation location; + + public JavaInvocationValidator(ClassReaderSource classSource, Diagnostics diagnostics) { + this.classSource = classSource; + this.diagnostics = diagnostics; + } + + public void validate(CallLocation location, AstNode root) { + this.location = location; + root.visit(this); + } + + @Override + public boolean visit(AstNode node) { + if (node instanceof FunctionCall) { + return validateCall((FunctionCall) node); + } + return true; + } + + private boolean validateCall(FunctionCall call) { + if (!(call.getTarget() instanceof PropertyGet)) { + return true; + } + + PropertyGet propertyGet = (PropertyGet) call.getTarget(); + MethodReference methodRef = getJavaMethodSelector(propertyGet.getTarget()); + if (methodRef == null || !propertyGet.getProperty().getIdentifier().equals("invoke")) { + return true; + } + + for (AstNode arg : call.getArguments()) { + arg.visit(this); + } + + MethodReader method = classSource.resolve(methodRef); + if (method == null) { + diagnostics.error(location, "Java method not found: {{m0}}", methodRef); + return false; + } + + int requiredParams = methodRef.parameterCount(); + if (!method.hasModifier(ElementModifier.STATIC)) { + ++requiredParams; + } + if (call.getArguments().size() != requiredParams) { + diagnostics.error(location, "Invalid number of arguments for method {{m0}}. Expected: " + requiredParams + + ", encountered: " + call.getArguments().size(), methodRef); + } + + StringBuilder sb = new StringBuilder("$$JSO$$_"); + sb.append(method.hasModifier(ElementModifier.STATIC) ? 'S' : "V"); + sb.append(method.getReference().toString()); + StringLiteral newTarget = new StringLiteral(); + newTarget.setValue(sb.toString()); + propertyGet.setTarget(newTarget); + + return false; + } + + private MethodReference getJavaMethodSelector(AstNode node) { + if (!(node instanceof FunctionCall)) { + return null; + } + FunctionCall call = (FunctionCall) node; + if (!isJavaMethodRepository(call.getTarget())) { + return null; + } + if (call.getArguments().size() != 1) { + diagnostics.error(location, "javaMethods.get method should take exactly one argument"); + return null; + } + StringBuilder nameBuilder = new StringBuilder(); + if (!extractMethodName(call.getArguments().get(0), nameBuilder)) { + diagnostics.error(location, "javaMethods.get method should take string constant"); + return null; + } + + MethodReference method = MethodReference.parseIfPossible(nameBuilder.toString()); + if (method == null) { + diagnostics.error(location, "Wrong method reference: " + nameBuilder); + } + return method; + } + + private boolean extractMethodName(AstNode node, StringBuilder sb) { + if (node.getType() == Token.ADD) { + InfixExpression infix = (InfixExpression) node; + return extractMethodName(infix.getLeft(), sb) && extractMethodName(infix.getRight(), sb); + } else if (node.getType() == Token.STRING) { + sb.append(((StringLiteral) node).getValue()); + return true; + } else { + return false; + } + } + + private boolean isJavaMethodRepository(AstNode node) { + if (!(node instanceof PropertyGet)) { + return false; + } + PropertyGet propertyGet = (PropertyGet) node; + + if (!(propertyGet.getLeft() instanceof Name)) { + return false; + } + if (!((Name) propertyGet.getTarget()).getIdentifier().equals("javaMethods")) { + return false; + } + if (!propertyGet.getProperty().getIdentifier().equals("get")) { + return false; + } + + return true; + } +} 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 ff2dbcd13..25e0d665c 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 @@ -318,7 +318,7 @@ class JavascriptNativeProcessor { return false; } - if (method == null || method.hasModifier(ElementModifier.STATIC)) { + if (method.hasModifier(ElementModifier.STATIC)) { return false; } @@ -579,6 +579,8 @@ class JavascriptNativeProcessor { } else { expr = rootNode; } + JavaInvocationValidator javaValidator = new JavaInvocationValidator(classSource, diagnostics); + javaValidator.validate(location, expr); repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, parameterNames)); } repository.methodMap.put(methodToProcess.getReference(), proxyMethod); diff --git a/teavm-jso-impl/src/test/java/org/teavm/jso/plugin/AstWriterTest.java b/teavm-jso-impl/src/test/java/org/teavm/jso/plugin/AstWriterTest.java index 51a42b1d9..22b786acd 100644 --- a/teavm-jso-impl/src/test/java/org/teavm/jso/plugin/AstWriterTest.java +++ b/teavm-jso-impl/src/test/java/org/teavm/jso/plugin/AstWriterTest.java @@ -39,7 +39,7 @@ public class AstWriterTest { SourceWriterBuilder builder = new SourceWriterBuilder(null); builder.setMinified(true); sourceWriter = builder.build(sb); - writer = new AstWriter(null, sourceWriter); + writer = new AstWriter(sourceWriter); } @Test diff --git a/teavm-jso/src/test/java/org/teavm/jso/test/JavaInvocationTest.java b/teavm-jso/src/test/java/org/teavm/jso/test/JavaInvocationTest.java new file mode 100644 index 000000000..984c0de64 --- /dev/null +++ b/teavm-jso/src/test/java/org/teavm/jso/test/JavaInvocationTest.java @@ -0,0 +1,39 @@ +/* + * 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.test; + +import static org.junit.Assert.*; +import org.junit.Test; +import org.teavm.jso.JSBody; + +/** + * + * @author Alexey Andreev + */ +public class JavaInvocationTest { + @Test + public void callStaticMethod() { + assertEquals(7, staticInvocation(5)); + } + + @JSBody(params = { "a" }, script = "return javaMethods.get('org.teavm.jso.test.JavaInvocationTest.sum(II)I')" + + ".invoke(a, 2);") + private static native int staticInvocation(int a); + + private static int sum(int a, int b) { + return a + b; + } +}