mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
First working prototype of JS to Java call with dependency checking and
type validation
This commit is contained in:
parent
6986f1c02a
commit
1e05cb42ea
|
@ -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<>();
|
||||
}
|
||||
|
|
|
@ -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,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<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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user