First working prototype of JS to Java call with dependency checking and

type validation
This commit is contained in:
Alexey Andreev 2015-09-27 13:23:29 +03:00
parent 6986f1c02a
commit 1e05cb42ea
6 changed files with 162 additions and 45 deletions

View File

@ -30,4 +30,7 @@ class JSBodyRepository {
public final Map<MethodReference, MethodReference> methodMap = new HashMap<>();
public final Set<MethodReference> processedMethods = new HashSet<>();
public final Set<MethodReference> inlineMethods = new HashSet<>();
public final Map<MethodReference, MethodReference> callbackCallees = new HashMap<>();
public final Map<MethodReference, Set<MethodReference>> callbackMethods = new HashMap<>();
public final Map<MethodReference, Set<MethodReference>> callbackMethodsDeps = new HashMap<>();
}

View File

@ -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<Instruction> replacement = new ArrayList<>();
private JSTypeHelper typeHelper;
@ -87,9 +90,12 @@ class JSClassProcessor {
private int methodIndexGenerator;
private Map<MethodReference, MethodReader> 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,20 +581,23 @@ 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())) {
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);
@ -605,11 +609,59 @@ class JSClassProcessor {
new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
proxyMethod.getAnnotations().add(generatorAnnot);
cls.addMethod(proxyMethod);
Set<MethodReference> 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);

View File

@ -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<String, ExposedClass> 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<MethodReference> 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);

View File

@ -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);

View File

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

View File

@ -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;