From 1e05cb42ea99ebfdc01fc1d3a473b7191fe25eef Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 27 Sep 2015 13:23:29 +0300 Subject: [PATCH] First working prototype of JS to Java call with dependency checking and type validation --- .../org/teavm/jso/impl/JSBodyRepository.java | 3 + .../org/teavm/jso/impl/JSClassProcessor.java | 122 +++++++++++++----- .../teavm/jso/impl/JSDependencyListener.java | 17 +++ .../java/org/teavm/jso/impl/JSOPlugin.java | 2 +- .../jso/impl/JSObjectClassTransformer.java | 7 +- .../jso/impl/JavaInvocationProcessor.java | 56 +++++++- 6 files changed, 162 insertions(+), 45 deletions(-) diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java index dee6d002b..10563521b 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java @@ -30,4 +30,7 @@ class JSBodyRepository { public final Map methodMap = new HashMap<>(); public final Set processedMethods = new HashSet<>(); public final Set inlineMethods = new HashSet<>(); + public final Map callbackCallees = new HashMap<>(); + public final Map> callbackMethods = new HashMap<>(); + public final Map> callbackMethodsDeps = new HashMap<>(); } diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index bbb4bae90..a2e68c33b 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -67,6 +67,7 @@ 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; @@ -80,6 +81,8 @@ import org.teavm.model.util.ProgramUtils; */ class JSClassProcessor { private ClassReaderSource classSource; + private JSBodyRepository repository; + private JavaInvocationProcessor javaInvocationProcessor; private Program program; private List replacement = new ArrayList<>(); private JSTypeHelper typeHelper; @@ -87,9 +90,12 @@ class JSClassProcessor { private int methodIndexGenerator; private Map overridenMethodCache = new HashMap<>(); - public JSClassProcessor(ClassReaderSource classSource) { + public JSClassProcessor(ClassReaderSource classSource, JSBodyRepository repository, Diagnostics diagnostics) { this.classSource = classSource; + this.repository = repository; + this.diagnostics = diagnostics; typeHelper = new JSTypeHelper(classSource); + javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics); } public ClassReaderSource getClassSource() { @@ -104,10 +110,6 @@ class JSClassProcessor { return typeHelper.isJavaScriptImplementation(className); } - public void setDiagnostics(Diagnostics diagnostics) { - this.diagnostics = diagnostics; - } - public MethodReference isFunctor(String className) { if (!typeHelper.isJavaScriptImplementation(className)) { return null; @@ -281,7 +283,7 @@ class JSClassProcessor { return staticSignature; } - public void processProgram(JSBodyRepository repository, MethodHolder methodToProcess) { + public void processProgram(MethodHolder methodToProcess) { program = methodToProcess.getProgram(); for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlock block = program.basicBlockAt(i); @@ -299,7 +301,7 @@ class JSClassProcessor { } CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); replacement.clear(); - if (processInvocation(repository, method, callLocation, invoke)) { + if (processInvocation(method, callLocation, invoke)) { block.getInstructions().set(j, replacement.get(0)); block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size())); j += replacement.size() - 1; @@ -308,10 +310,9 @@ class JSClassProcessor { } } - private boolean processInvocation(JSBodyRepository repository, MethodReader method, CallLocation callLocation, - InvokeInstruction invoke) { + private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { if (method.getAnnotations().get(JSBody.class.getName()) != null) { - return processJSBodyInvocation(repository, method, callLocation, invoke); + return processJSBodyInvocation(method, callLocation, invoke); } if (!typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) { @@ -348,9 +349,8 @@ class JSClassProcessor { } } - private boolean processJSBodyInvocation(JSBodyRepository repository, MethodReader method, - CallLocation callLocation, InvokeInstruction invoke) { - requireJSBody(repository, diagnostics, method); + private boolean processJSBodyInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) { + requireJSBody(diagnostics, method); MethodReference delegate = repository.methodMap.get(method.getReference()); if (delegate == null) { return false; @@ -491,14 +491,14 @@ class JSClassProcessor { return true; } - private void requireJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) { + private void requireJSBody(Diagnostics diagnostics, MethodReader methodToProcess) { if (!repository.processedMethods.add(methodToProcess.getReference())) { return; } - processJSBody(repository, diagnostics, methodToProcess); + processJSBody(diagnostics, methodToProcess); } - private void processJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) { + private void processJSBody(Diagnostics diagnostics, MethodReader methodToProcess) { CallLocation location = new CallLocation(methodToProcess.getReference()); boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC); @@ -569,6 +569,7 @@ class JSClassProcessor { } parser.exitFunction(); + repository.methodMap.put(methodToProcess.getReference(), proxyMethod); if (errorReporter.hasErrors()) { repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod, script, parameterNames)); @@ -580,36 +581,87 @@ class JSClassProcessor { } else { expr = rootNode; } - JavaInvocationProcessor javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, - classSource, diagnostics); - javaInvocationProcessor.validate(location, expr); + javaInvocationProcessor.process(location, expr); repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, parameterNames)); } - repository.methodMap.put(methodToProcess.getReference(), proxyMethod); } - public void createJSMethods(JSBodyRepository repository, ClassHolder cls) { + public void createJSMethods(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); - boolean inline = repository.inlineMethods.contains(methodRef); - AnnotationHolder generatorAnnot = new AnnotationHolder(inline - ? InjectedBy.class.getName() : GeneratedBy.class.getName()); - generatorAnnot.getValues().put("value", - new AnnotationValue(ValueType.parse(JSBodyGenerator.class))); - proxyMethod.getAnnotations().add(generatorAnnot); - cls.addMethod(proxyMethod); + if (method.getAnnotations().get(JSBody.class.getName()) == null) { + continue; + } + + requireJSBody(diagnostics, method); + if (!repository.methodMap.containsKey(method.getReference())) { + continue; + } + + MethodReference proxyRef = repository.methodMap.get(methodRef); + MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor()); + proxyMethod.getModifiers().add(ElementModifier.NATIVE); + proxyMethod.getModifiers().add(ElementModifier.STATIC); + boolean inline = repository.inlineMethods.contains(methodRef); + AnnotationHolder generatorAnnot = new AnnotationHolder(inline + ? InjectedBy.class.getName() : GeneratedBy.class.getName()); + generatorAnnot.getValues().put("value", + new AnnotationValue(ValueType.parse(JSBodyGenerator.class))); + proxyMethod.getAnnotations().add(generatorAnnot); + cls.addMethod(proxyMethod); + + Set callbacks = repository.callbackMethods.get(proxyRef); + if (callbacks != null) { + for (MethodReference callback : callbacks) { + generateCallbackCaller(cls, callback); } } } } + private void generateCallbackCaller(ClassHolder cls, MethodReference callback) { + MethodReference calleeRef = repository.callbackCallees.get(callback); + MethodReader callee = classSource.resolve(calleeRef); + MethodHolder callerMethod = new MethodHolder(callback.getDescriptor()); + callerMethod.getModifiers().add(ElementModifier.STATIC); + CallLocation location = new CallLocation(callback); + + program = new Program(); + for (int i = 0; i <= callback.parameterCount(); ++i) { + program.createVariable(); + } + BasicBlock block = program.createBasicBlock(); + + int paramIndex = 1; + InvokeInstruction insn = new InvokeInstruction(); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(calleeRef); + replacement.clear(); + if (!callee.hasModifier(ElementModifier.STATIC)) { + insn.setInstance(unwrap(location, program.variableAt(paramIndex++), + ValueType.object(calleeRef.getClassName()))); + } + for (int i = 0; i < callee.parameterCount(); ++i) { + insn.getArguments().add(unwrap(location, program.variableAt(paramIndex++), callee.parameterType(i))); + } + if (callee.getResultType() != ValueType.VOID) { + insn.setReceiver(program.createVariable()); + } + block.getInstructions().addAll(replacement); + block.getInstructions().add(insn); + + ExitInstruction exit = new ExitInstruction(); + if (insn.getReceiver() != null) { + replacement.clear(); + exit.setValueToReturn(wrap(insn.getReceiver(), callee.getResultType(), null)); + block.getInstructions().addAll(replacement); + } + block.getInstructions().add(exit); + + callerMethod.setProgram(program); + cls.addMethod(callerMethod); + } + private void addPropertyGet(String propertyName, Variable instance, Variable receiver, InstructionLocation location) { Variable nameVar = addStringWrap(addString(propertyName, location), location); diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java index 826794fac..56d79866c 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java @@ -33,6 +33,7 @@ import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; /** * @@ -42,14 +43,30 @@ class JSDependencyListener extends AbstractDependencyListener { private Map exposedClasses = new HashMap<>(); private ClassReaderSource classSource; private DependencyAgent agent; + private JSBodyRepository repository; private boolean anyAliasExists; + public JSDependencyListener(JSBodyRepository repository) { + this.repository = repository; + } + @Override public void started(DependencyAgent agent) { this.agent = agent; classSource = agent.getClassSource(); } + @Override + public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + MethodReference ref = method.getReference(); + Set callbackMethods = repository.callbackMethods.get(ref); + if (callbackMethods != null) { + for (MethodReference callbackMethod : callbackMethods) { + agent.linkMethod(callbackMethod, new CallLocation(ref)).use(); + } + } + } + @Override public void classReached(DependencyAgent agent, String className, CallLocation location) { getExposedClass(className); diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java index d01e1c8bc..4535f4349 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java @@ -28,7 +28,7 @@ public class JSOPlugin implements TeaVMPlugin { JSBodyRepository repository = new JSBodyRepository(); host.registerService(JSBodyRepository.class, repository); host.add(new JSObjectClassTransformer(repository)); - JSDependencyListener dependencyListener = new JSDependencyListener(); + JSDependencyListener dependencyListener = new JSDependencyListener(repository); JSAliasRenderer aliasRenderer = new JSAliasRenderer(dependencyListener); host.add(dependencyListener); host.add(aliasRenderer); diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index 323bc3e54..fbabe2544 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -37,9 +37,8 @@ public class JSObjectClassTransformer implements ClassHolderTransformer { @Override public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { if (processor == null || processor.getClassSource() != innerSource) { - processor = new JSClassProcessor(innerSource); + processor = new JSClassProcessor(innerSource, repository, diagnostics); } - processor.setDiagnostics(diagnostics); processor.processClass(cls); if (processor.isNative(cls.getName())) { processor.processFinalMethods(cls); @@ -55,9 +54,9 @@ public class JSObjectClassTransformer implements ClassHolderTransformer { } for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { if (method.getProgram() != null) { - processor.processProgram(repository, method); + processor.processProgram(method); } } - processor.createJSMethods(repository, cls); + processor.createJSMethods(cls); } } diff --git a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JavaInvocationProcessor.java b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JavaInvocationProcessor.java index a469f5dc8..872fdfe69 100644 --- a/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JavaInvocationProcessor.java +++ b/teavm-jso-impl/src/main/java/org/teavm/jso/impl/JavaInvocationProcessor.java @@ -15,6 +15,7 @@ */ package org.teavm.jso.impl; +import java.util.HashSet; import org.mozilla.javascript.Token; import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.FunctionCall; @@ -24,11 +25,13 @@ 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.jso.JSObject; 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; +import org.teavm.model.ValueType; /** * @@ -37,16 +40,20 @@ import org.teavm.model.MethodReference; class JavaInvocationProcessor implements NodeVisitor { private ClassReaderSource classSource; private JSTypeHelper typeHelper; + private JSBodyRepository repository; private Diagnostics diagnostics; private CallLocation location; + private int idGenerator; - public JavaInvocationProcessor(JSTypeHelper typeHelper, ClassReaderSource classSource, Diagnostics diagnostics) { + public JavaInvocationProcessor(JSTypeHelper typeHelper, JSBodyRepository repository, + ClassReaderSource classSource, Diagnostics diagnostics) { this.typeHelper = typeHelper; + this.repository = repository; this.classSource = classSource; this.diagnostics = diagnostics; } - public void validate(CallLocation location, AstNode root) { + public void process(CallLocation location, AstNode root) { this.location = location; root.visit(this); } @@ -54,12 +61,12 @@ class JavaInvocationProcessor implements NodeVisitor { @Override public boolean visit(AstNode node) { if (node instanceof FunctionCall) { - return validateCall((FunctionCall) node); + return visit((FunctionCall) node); } return true; } - private boolean validateCall(FunctionCall call) { + private boolean visit(FunctionCall call) { if (!(call.getTarget() instanceof PropertyGet)) { return true; } @@ -89,9 +96,15 @@ class JavaInvocationProcessor implements NodeVisitor { + ", encountered: " + call.getArguments().size(), methodRef); } + MethodReference caller = createCallbackMethod(method); + MethodReference delegate = repository.methodMap.get(location.getMethod()); + repository.callbackCallees.put(caller, methodRef); + repository.callbackMethods.computeIfAbsent(delegate, key -> new HashSet<>()).add(caller); + validateSignature(method); + StringBuilder sb = new StringBuilder("$$JSO$$_"); sb.append(method.hasModifier(ElementModifier.STATIC) ? 'S' : "V"); - sb.append(method.getReference().toString()); + sb.append(caller.toString()); StringLiteral newTarget = new StringLiteral(); newTarget.setValue(sb.toString()); propertyGet.setTarget(newTarget); @@ -99,6 +112,39 @@ class JavaInvocationProcessor implements NodeVisitor { return false; } + private void validateSignature(MethodReader method) { + if (!method.hasModifier(ElementModifier.STATIC)) { + if (!typeHelper.isJavaScriptClass(method.getOwnerName())) { + diagnostics.error(location, "Can't call method {{m0}} of non-JS class", method.getReference()); + } + } + for (int i = 0; i < method.parameterCount(); ++i) { + if (!typeHelper.isSupportedType(method.parameterType(i))) { + diagnostics.error(location, "Invalid type {{t0}} of parameter " + (i + 1) + " of method {{m1}}", + method.parameterType(i), method.getReference()); + } + } + if (method.getResultType() != ValueType.VOID && !typeHelper.isSupportedType(method.getResultType())) { + diagnostics.error(location, "Invalid type {{t0}} of return value of method {{m1}}", method.getResultType(), + method.getReference()); + } + } + + private MethodReference createCallbackMethod(MethodReader method) { + int paramCount = method.parameterCount(); + if (!method.hasModifier(ElementModifier.STATIC)) { + paramCount++; + } + ValueType[] signature = new ValueType[paramCount + 1]; + for (int i = 0; i < paramCount; ++i) { + signature[i] = ValueType.object(JSObject.class.getName()); + } + signature[paramCount] = method.getResultType() == ValueType.VOID ? ValueType.VOID + : ValueType.object(JSObject.class.getName()); + return new MethodReference(location.getMethod().getClassName(), + method.getName() + "$jsocb$_" + idGenerator++, signature); + } + private MethodReference getJavaMethodSelector(AstNode node) { if (!(node instanceof FunctionCall)) { return null;