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 Map<MethodReference, MethodReference> methodMap = new HashMap<>();
public final Set<MethodReference> processedMethods = new HashSet<>(); public final Set<MethodReference> processedMethods = new HashSet<>();
public final Set<MethodReference> inlineMethods = 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.AssignInstruction;
import org.teavm.model.instructions.CastInstruction; import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.instructions.StringConstantInstruction;
@ -80,6 +81,8 @@ import org.teavm.model.util.ProgramUtils;
*/ */
class JSClassProcessor { class JSClassProcessor {
private ClassReaderSource classSource; private ClassReaderSource classSource;
private JSBodyRepository repository;
private JavaInvocationProcessor javaInvocationProcessor;
private Program program; private Program program;
private List<Instruction> replacement = new ArrayList<>(); private List<Instruction> replacement = new ArrayList<>();
private JSTypeHelper typeHelper; private JSTypeHelper typeHelper;
@ -87,9 +90,12 @@ class JSClassProcessor {
private int methodIndexGenerator; private int methodIndexGenerator;
private Map<MethodReference, MethodReader> overridenMethodCache = new HashMap<>(); private Map<MethodReference, MethodReader> overridenMethodCache = new HashMap<>();
public JSClassProcessor(ClassReaderSource classSource) { public JSClassProcessor(ClassReaderSource classSource, JSBodyRepository repository, Diagnostics diagnostics) {
this.classSource = classSource; this.classSource = classSource;
this.repository = repository;
this.diagnostics = diagnostics;
typeHelper = new JSTypeHelper(classSource); typeHelper = new JSTypeHelper(classSource);
javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics);
} }
public ClassReaderSource getClassSource() { public ClassReaderSource getClassSource() {
@ -104,10 +110,6 @@ class JSClassProcessor {
return typeHelper.isJavaScriptImplementation(className); return typeHelper.isJavaScriptImplementation(className);
} }
public void setDiagnostics(Diagnostics diagnostics) {
this.diagnostics = diagnostics;
}
public MethodReference isFunctor(String className) { public MethodReference isFunctor(String className) {
if (!typeHelper.isJavaScriptImplementation(className)) { if (!typeHelper.isJavaScriptImplementation(className)) {
return null; return null;
@ -281,7 +283,7 @@ class JSClassProcessor {
return staticSignature; return staticSignature;
} }
public void processProgram(JSBodyRepository repository, MethodHolder methodToProcess) { public void processProgram(MethodHolder methodToProcess) {
program = methodToProcess.getProgram(); program = methodToProcess.getProgram();
for (int i = 0; i < program.basicBlockCount(); ++i) { for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i); BasicBlock block = program.basicBlockAt(i);
@ -299,7 +301,7 @@ class JSClassProcessor {
} }
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation()); CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
replacement.clear(); replacement.clear();
if (processInvocation(repository, method, callLocation, invoke)) { if (processInvocation(method, callLocation, invoke)) {
block.getInstructions().set(j, replacement.get(0)); block.getInstructions().set(j, replacement.get(0));
block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size())); block.getInstructions().addAll(j + 1, replacement.subList(1, replacement.size()));
j += replacement.size() - 1; j += replacement.size() - 1;
@ -308,10 +310,9 @@ class JSClassProcessor {
} }
} }
private boolean processInvocation(JSBodyRepository repository, MethodReader method, CallLocation callLocation, private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
InvokeInstruction invoke) {
if (method.getAnnotations().get(JSBody.class.getName()) != null) { 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())) { if (!typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) {
@ -348,9 +349,8 @@ class JSClassProcessor {
} }
} }
private boolean processJSBodyInvocation(JSBodyRepository repository, MethodReader method, private boolean processJSBodyInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
CallLocation callLocation, InvokeInstruction invoke) { requireJSBody(diagnostics, method);
requireJSBody(repository, diagnostics, method);
MethodReference delegate = repository.methodMap.get(method.getReference()); MethodReference delegate = repository.methodMap.get(method.getReference());
if (delegate == null) { if (delegate == null) {
return false; return false;
@ -491,14 +491,14 @@ class JSClassProcessor {
return true; return true;
} }
private void requireJSBody(JSBodyRepository repository, Diagnostics diagnostics, MethodReader methodToProcess) { private void requireJSBody(Diagnostics diagnostics, MethodReader methodToProcess) {
if (!repository.processedMethods.add(methodToProcess.getReference())) { if (!repository.processedMethods.add(methodToProcess.getReference())) {
return; 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()); CallLocation location = new CallLocation(methodToProcess.getReference());
boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC); boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);
@ -569,6 +569,7 @@ class JSClassProcessor {
} }
parser.exitFunction(); parser.exitFunction();
repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
if (errorReporter.hasErrors()) { if (errorReporter.hasErrors()) {
repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod, repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod,
script, parameterNames)); script, parameterNames));
@ -580,36 +581,87 @@ class JSClassProcessor {
} else { } else {
expr = rootNode; expr = rootNode;
} }
JavaInvocationProcessor javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, javaInvocationProcessor.process(location, expr);
classSource, diagnostics);
javaInvocationProcessor.validate(location, expr);
repository.emitters.put(proxyMethod, new JSBodyAstEmitter(isStatic, expr, parameterNames)); 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])) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
MethodReference methodRef = method.getReference(); MethodReference methodRef = method.getReference();
if (method.getAnnotations().get(JSBody.class.getName()) != null) { if (method.getAnnotations().get(JSBody.class.getName()) == null) {
requireJSBody(repository, diagnostics, method); continue;
if (repository.methodMap.containsKey(method.getReference())) { }
MethodReference proxyRef = repository.methodMap.get(methodRef);
MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor()); requireJSBody(diagnostics, method);
proxyMethod.getModifiers().add(ElementModifier.NATIVE); if (!repository.methodMap.containsKey(method.getReference())) {
proxyMethod.getModifiers().add(ElementModifier.STATIC); continue;
boolean inline = repository.inlineMethods.contains(methodRef); }
AnnotationHolder generatorAnnot = new AnnotationHolder(inline
? InjectedBy.class.getName() : GeneratedBy.class.getName()); MethodReference proxyRef = repository.methodMap.get(methodRef);
generatorAnnot.getValues().put("value", MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor());
new AnnotationValue(ValueType.parse(JSBodyGenerator.class))); proxyMethod.getModifiers().add(ElementModifier.NATIVE);
proxyMethod.getAnnotations().add(generatorAnnot); proxyMethod.getModifiers().add(ElementModifier.STATIC);
cls.addMethod(proxyMethod); 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<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, private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
InstructionLocation location) { InstructionLocation location) {
Variable nameVar = addStringWrap(addString(propertyName, location), 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.FieldReference;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader; 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 Map<String, ExposedClass> exposedClasses = new HashMap<>();
private ClassReaderSource classSource; private ClassReaderSource classSource;
private DependencyAgent agent; private DependencyAgent agent;
private JSBodyRepository repository;
private boolean anyAliasExists; private boolean anyAliasExists;
public JSDependencyListener(JSBodyRepository repository) {
this.repository = repository;
}
@Override @Override
public void started(DependencyAgent agent) { public void started(DependencyAgent agent) {
this.agent = agent; this.agent = agent;
classSource = agent.getClassSource(); 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 @Override
public void classReached(DependencyAgent agent, String className, CallLocation location) { public void classReached(DependencyAgent agent, String className, CallLocation location) {
getExposedClass(className); getExposedClass(className);

View File

@ -28,7 +28,7 @@ public class JSOPlugin implements TeaVMPlugin {
JSBodyRepository repository = new JSBodyRepository(); JSBodyRepository repository = new JSBodyRepository();
host.registerService(JSBodyRepository.class, repository); host.registerService(JSBodyRepository.class, repository);
host.add(new JSObjectClassTransformer(repository)); host.add(new JSObjectClassTransformer(repository));
JSDependencyListener dependencyListener = new JSDependencyListener(); JSDependencyListener dependencyListener = new JSDependencyListener(repository);
JSAliasRenderer aliasRenderer = new JSAliasRenderer(dependencyListener); JSAliasRenderer aliasRenderer = new JSAliasRenderer(dependencyListener);
host.add(dependencyListener); host.add(dependencyListener);
host.add(aliasRenderer); host.add(aliasRenderer);

View File

@ -37,9 +37,8 @@ public class JSObjectClassTransformer implements ClassHolderTransformer {
@Override @Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
if (processor == null || processor.getClassSource() != innerSource) { if (processor == null || processor.getClassSource() != innerSource) {
processor = new JSClassProcessor(innerSource); processor = new JSClassProcessor(innerSource, repository, diagnostics);
} }
processor.setDiagnostics(diagnostics);
processor.processClass(cls); processor.processClass(cls);
if (processor.isNative(cls.getName())) { if (processor.isNative(cls.getName())) {
processor.processFinalMethods(cls); processor.processFinalMethods(cls);
@ -55,9 +54,9 @@ public class JSObjectClassTransformer implements ClassHolderTransformer {
} }
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
if (method.getProgram() != null) { 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; package org.teavm.jso.impl;
import java.util.HashSet;
import org.mozilla.javascript.Token; import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.FunctionCall; 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.PropertyGet;
import org.mozilla.javascript.ast.StringLiteral; import org.mozilla.javascript.ast.StringLiteral;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.jso.JSObject;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
/** /**
* *
@ -37,16 +40,20 @@ import org.teavm.model.MethodReference;
class JavaInvocationProcessor implements NodeVisitor { class JavaInvocationProcessor implements NodeVisitor {
private ClassReaderSource classSource; private ClassReaderSource classSource;
private JSTypeHelper typeHelper; private JSTypeHelper typeHelper;
private JSBodyRepository repository;
private Diagnostics diagnostics; private Diagnostics diagnostics;
private CallLocation location; 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.typeHelper = typeHelper;
this.repository = repository;
this.classSource = classSource; this.classSource = classSource;
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
} }
public void validate(CallLocation location, AstNode root) { public void process(CallLocation location, AstNode root) {
this.location = location; this.location = location;
root.visit(this); root.visit(this);
} }
@ -54,12 +61,12 @@ class JavaInvocationProcessor implements NodeVisitor {
@Override @Override
public boolean visit(AstNode node) { public boolean visit(AstNode node) {
if (node instanceof FunctionCall) { if (node instanceof FunctionCall) {
return validateCall((FunctionCall) node); return visit((FunctionCall) node);
} }
return true; return true;
} }
private boolean validateCall(FunctionCall call) { private boolean visit(FunctionCall call) {
if (!(call.getTarget() instanceof PropertyGet)) { if (!(call.getTarget() instanceof PropertyGet)) {
return true; return true;
} }
@ -89,9 +96,15 @@ class JavaInvocationProcessor implements NodeVisitor {
+ ", encountered: " + call.getArguments().size(), methodRef); + ", 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$$_"); StringBuilder sb = new StringBuilder("$$JSO$$_");
sb.append(method.hasModifier(ElementModifier.STATIC) ? 'S' : "V"); sb.append(method.hasModifier(ElementModifier.STATIC) ? 'S' : "V");
sb.append(method.getReference().toString()); sb.append(caller.toString());
StringLiteral newTarget = new StringLiteral(); StringLiteral newTarget = new StringLiteral();
newTarget.setValue(sb.toString()); newTarget.setValue(sb.toString());
propertyGet.setTarget(newTarget); propertyGet.setTarget(newTarget);
@ -99,6 +112,39 @@ class JavaInvocationProcessor implements NodeVisitor {
return false; 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) { private MethodReference getJavaMethodSelector(AstNode node) {
if (!(node instanceof FunctionCall)) { if (!(node instanceof FunctionCall)) {
return null; return null;