From 84d9899b3edf6d8b0121cb09ec58744dfcfef764 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 6 Apr 2016 23:02:15 +0300 Subject: [PATCH] Implement metaprogramming proxies --- classlib/teavm-classlib.iml | 2 +- .../impl/MetaprogrammingImpl.java | 91 ++++++++++- .../impl/ProxyVariableContext.java | 150 ++++++++++++++++++ .../metaprogramming/InvocationHandler.java | 2 +- .../teavm/metaprogramming/test/ProxyTest.java | 120 ++++++++++++++ tests/teavm-tests.iml | 8 + tools/core/teavm-tooling.iml | 2 +- tools/junit/teavm-junit.iml | 8 + 8 files changed, 378 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/org/teavm/metaprogramming/impl/ProxyVariableContext.java create mode 100644 tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java diff --git a/classlib/teavm-classlib.iml b/classlib/teavm-classlib.iml index ead5bc4ab..3769e7ea4 100644 --- a/classlib/teavm-classlib.iml +++ b/classlib/teavm-classlib.iml @@ -14,7 +14,7 @@ - + diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java index 257f58e0e..8d6ce918a 100644 --- a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java +++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java @@ -15,6 +15,8 @@ */ package org.teavm.metaprogramming.impl; +import java.util.HashMap; +import java.util.Map; import org.teavm.dependency.DependencyAgent; import org.teavm.metaprogramming.Action; import org.teavm.metaprogramming.Computation; @@ -28,14 +30,19 @@ import org.teavm.metaprogramming.impl.reflect.ReflectClassImpl; import org.teavm.metaprogramming.impl.reflect.ReflectContext; import org.teavm.metaprogramming.impl.reflect.ReflectFieldImpl; import org.teavm.metaprogramming.impl.reflect.ReflectMethodImpl; +import org.teavm.metaprogramming.reflect.ReflectMethod; +import org.teavm.model.AccessLevel; import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; import org.teavm.model.Instruction; import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; +import org.teavm.model.Program; import org.teavm.model.ValueType; import org.teavm.model.Variable; import org.teavm.model.instructions.DoubleConstantInstruction; @@ -44,11 +51,13 @@ import org.teavm.model.instructions.FloatConstantInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.JumpInstruction; import org.teavm.model.instructions.LongConstantInstruction; import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.util.InstructionTransitionExtractor; public final class MetaprogrammingImpl { + static Map proxySuffixGenerators = new HashMap<>(); static ClassLoader classLoader; static ClassReaderSource classSource; static ReflectContext reflectContext; @@ -207,8 +216,86 @@ public final class MetaprogrammingImpl { @SuppressWarnings("WeakerAccess") public static Value proxy(ReflectClass type, InvocationHandler handler) { - unsupported(); - return null; + ValueType innerType = ((ReflectClassImpl) type).type; + ClassHolder cls = new ClassHolder(createProxyName(type.getName())); + cls.setLevel(AccessLevel.PUBLIC); + + String typeName = ((ValueType.Object) innerType).getClassName(); + org.teavm.model.ClassReader typeReader = classSource.get(typeName); + if (typeReader.hasModifier(ElementModifier.INTERFACE)) { + cls.setParent("java.lang.Object"); + cls.getInterfaces().add(typeName); + } else { + cls.setParent(typeName); + } + + ProxyVariableContext nestedVarContext = new ProxyVariableContext(varContext, cls); + for (ReflectMethod method : type.getMethods()) { + ReflectMethodImpl methodImpl = (ReflectMethodImpl) method; + if (methodImpl.method.getProgram() != null && methodImpl.method.getProgram().basicBlockCount() > 0 + || methodImpl.method.hasModifier(ElementModifier.NATIVE) + || !methodImpl.method.hasModifier(ElementModifier.ABSTRACT)) { + continue; + } + + MethodHolder methodHolder = new MethodHolder(methodImpl.method.getDescriptor()); + methodHolder.setLevel(AccessLevel.PUBLIC); + + ValueType returnTypeBackup = returnType; + VariableContext varContextBackup = varContext; + CompositeMethodGenerator generatorBackup = generator; + try { + returnType = methodHolder.getResultType(); + varContext = nestedVarContext; + generator = new CompositeMethodGenerator(varContext, new Program()); + + Program program = generator.program; + program.createBasicBlock(); + generator.blockIndex = 0; + BasicBlock startBlock = generator.currentBlock(); + nestedVarContext.init(startBlock); + + methodHolder.setProgram(program); + Variable thisVar = program.createVariable(); + int argumentCount = methodImpl.method.parameterCount(); + @SuppressWarnings("unchecked") + ValueImpl[] arguments = (ValueImpl[]) new ValueImpl[argumentCount]; + for (int i = 0; i < arguments.length; ++i) { + arguments[i] = new ValueImpl<>(program.createVariable(), nestedVarContext, + methodImpl.method.parameterType(i)); + } + for (int i = 0; i < arguments.length; ++i) { + ValueType argType = methodImpl.method.parameterType(i); + Variable var = generator.box(arguments[i].innerValue, argType); + arguments[i] = new ValueImpl<>(var, nestedVarContext, argType); + } + + handler.invoke(new ValueImpl<>(thisVar, nestedVarContext, innerType), methodImpl, arguments); + close(); + + JumpInstruction jumpToStart = new JumpInstruction(); + jumpToStart.setTarget(program.basicBlockAt(startBlock.getIndex() + 1)); + startBlock.getInstructions().add(jumpToStart); + + //new BoxingEliminator().optimize(program); + cls.addMethod(methodHolder); + } finally { + returnType = returnTypeBackup; + varContext = varContextBackup; + generator = generatorBackup; + } + } + + ValueImpl result = new ValueImpl<>(nestedVarContext.createInstance(generator), varContext, innerType); + + agent.submitClass(cls); + return result; + } + + private static String createProxyName(String className) { + int suffix = proxySuffixGenerators.getOrDefault(className, 0); + proxySuffixGenerators.put(className, suffix + 1); + return className + "$proxy" + suffix; } private static void returnValue(Variable var) { diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/ProxyVariableContext.java b/core/src/main/java/org/teavm/metaprogramming/impl/ProxyVariableContext.java new file mode 100644 index 000000000..f882db362 --- /dev/null +++ b/core/src/main/java/org/teavm/metaprogramming/impl/ProxyVariableContext.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 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.metaprogramming.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.model.AccessLevel; +import org.teavm.model.BasicBlock; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassHolder; +import org.teavm.model.FieldHolder; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.PutFieldInstruction; + +public class ProxyVariableContext extends VariableContext { + private Map, Variable> cache = new HashMap<>(); + private BasicBlock startBlock; + private ClassHolder proxyClass; + private int suffixGenerator; + private Map capturedValueMap = new HashMap<>(); + private List capturedValues = new ArrayList<>(); + + public ProxyVariableContext(VariableContext parent, ClassHolder proxyClass) { + super(parent); + this.proxyClass = proxyClass; + } + + public void init(BasicBlock startBlock) { + this.startBlock = startBlock; + cache.clear(); + } + + @Override + public Variable emitVariable(ValueImpl value, CallLocation location) { + return cache.computeIfAbsent(value, v -> createVariable(v, location)); + } + + private Variable createVariable(ValueImpl value, CallLocation location) { + if (value.context == this) { + return value.innerValue; + } + Variable outerVar = getParent().emitVariable(value, location); + + CapturedValue capturedValue = capturedValueMap.computeIfAbsent(outerVar, v -> { + FieldHolder field = new FieldHolder("proxyCapture" + suffixGenerator++); + field.setLevel(AccessLevel.PUBLIC); + field.setType(value.type); + proxyClass.addField(field); + + CapturedValue result = new CapturedValue(field, v); + capturedValues.add(result); + return result; + }); + + Program program = startBlock.getProgram(); + Variable var = program.createVariable(); + GetFieldInstruction insn = new GetFieldInstruction(); + insn.setInstance(program.variableAt(0)); + insn.setField(capturedValue.field.getReference()); + insn.setFieldType(capturedValue.field.getType()); + insn.setReceiver(var); + startBlock.getInstructions().add(insn); + + return var; + } + + public Variable createInstance(CompositeMethodGenerator generator) { + ValueType[] signature = new ValueType[capturedValues.size() + 1]; + for (int i = 0; i < capturedValues.size(); ++i) { + signature[i] = capturedValues.get(i).field.getType(); + } + signature[capturedValues.size()] = ValueType.VOID; + + MethodHolder ctor = new MethodHolder("", signature); + ctor.setLevel(AccessLevel.PUBLIC); + Program ctorProgram = new Program(); + ctor.setProgram(ctorProgram); + BasicBlock ctorBlock = ctorProgram.createBasicBlock(); + + InvokeInstruction invokeSuper = new InvokeInstruction(); + invokeSuper.setInstance(ctorProgram.createVariable()); + invokeSuper.setMethod(new MethodReference(proxyClass.getParent(), "", ValueType.VOID)); + invokeSuper.setType(InvocationType.SPECIAL); + ctorBlock.getInstructions().add(invokeSuper); + + for (int i = 0; i < capturedValues.size(); ++i) { + PutFieldInstruction putInsn = new PutFieldInstruction(); + putInsn.setField(capturedValues.get(i).field.getReference()); + putInsn.setFieldType(capturedValues.get(i).field.getType()); + putInsn.setValue(ctorProgram.createVariable()); + putInsn.setInstance(ctorProgram.variableAt(0)); + ctorBlock.getInstructions().add(putInsn); + } + + ExitInstruction exit = new ExitInstruction(); + ctorBlock.getInstructions().add(exit); + + proxyClass.addMethod(ctor); + + ConstructInstruction constructInsn = new ConstructInstruction(); + constructInsn.setReceiver(generator.program.createVariable()); + constructInsn.setType(proxyClass.getName()); + generator.add(constructInsn); + + InvokeInstruction initInsn = new InvokeInstruction(); + initInsn.setInstance(constructInsn.getReceiver()); + initInsn.setMethod(ctor.getReference()); + initInsn.setType(InvocationType.SPECIAL); + for (int i = 0; i < capturedValues.size(); ++i) { + initInsn.getArguments().add(capturedValues.get(i).value); + } + generator.add(initInsn); + + return constructInsn.getReceiver(); + } + + class CapturedValue { + FieldHolder field; + Variable value; + + CapturedValue(FieldHolder field, Variable value) { + this.field = field; + this.value = value; + } + } +} diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java index 9bc3a81cf..2fbbaf4fa 100644 --- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java +++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/InvocationHandler.java @@ -18,5 +18,5 @@ package org.teavm.metaprogramming; import org.teavm.metaprogramming.reflect.ReflectMethod; public interface InvocationHandler { - Computation invoke(Value proxy, ReflectMethod method, Value[] args); + void invoke(Value proxy, ReflectMethod method, Value[] args); } diff --git a/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java b/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java new file mode 100644 index 000000000..1a4f3cd5a --- /dev/null +++ b/tests/src/test/java/org/teavm/metaprogramming/test/ProxyTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016 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.metaprogramming.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.teavm.metaprogramming.Metaprogramming.emit; +import static org.teavm.metaprogramming.Metaprogramming.exit; +import static org.teavm.metaprogramming.Metaprogramming.proxy; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.java.lang.StringBuilderTest; +import org.teavm.junit.SkipJVM; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.metaprogramming.CompileTime; +import org.teavm.metaprogramming.Meta; +import org.teavm.metaprogramming.ReflectClass; +import org.teavm.metaprogramming.Value; + +@CompileTime +@RunWith(TeaVMTestRunner.class) +@SkipJVM +public class ProxyTest { + @Test + public void createsProxy() { + A proxy = createProxy(A.class, "!"); + assertEquals("foo!", proxy.foo()); + assertEquals("bar!", proxy.bar()); + } + + @Meta + private static native T createProxy(Class proxyType, String add); + private static void createProxy(ReflectClass proxyType, Value add) { + Value proxy = proxy(proxyType, (instance, method, args) -> { + String name = method.getName(); + exit(() -> name + add.get()); + }); + exit(() -> proxy.get()); + } + + @Test + public void defaultReturnValue() { + StringBuilder log = new StringBuilder(); + B proxy = createProxyWithDefaultReturnValue(B.class, log); + assertNull(proxy.foo()); + assertEquals(0, proxy.bar()); + proxy.baz(); + + assertEquals("foo;bar;baz;", log.toString()); + } + + @Meta + private static native T createProxyWithDefaultReturnValue(Class proxyType, StringBuilder sb); + private static void createProxyWithDefaultReturnValue(ReflectClass proxyType, Value sb) { + Value proxy = proxy(proxyType, (instance, method, args) -> { + String name = method.getName(); + emit(() -> sb.get().append(name + ";")); + }); + exit(() -> proxy.get()); + } + + @Test + public void boxParameters() { + C proxy = createProxyWithBoxedParameters(C.class); + assertEquals("foo(true,0,1,2,3,4,5)", proxy.foo(true, (byte) 0, '1', (short) 2, 3, 4, "5")); + } + + @Meta + private static native T createProxyWithBoxedParameters(Class proxyType); + private static void createProxyWithBoxedParameters(ReflectClass proxyType) { + Value proxy = proxy(proxyType, (instance, method, args) -> { + Value result = emit(() -> new StringBuilder()); + + String name = method.getName(); + emit(() -> result.get().append(name).append('(')); + if (args.length > 0) { + emit(() -> result.get().append(args[0].get().toString())); + for (int i = 1; i < args.length; ++i) { + Value arg = args[i]; + emit(() -> result.get().append(',').append(arg.get().toString())); + } + } + emit(() -> result.get().append(')')); + + exit(() -> result.get().toString()); + }); + exit(() -> proxy.get()); + } + + interface A { + String foo(); + + String bar(); + } + + interface B { + String foo(); + + int bar(); + + void baz(); + } + + interface C { + String foo(boolean a, byte b, char c, short d, int e, long f, String g); + } +} diff --git a/tests/teavm-tests.iml b/tests/teavm-tests.iml index d00130667..8322ddcbb 100644 --- a/tests/teavm-tests.iml +++ b/tests/teavm-tests.iml @@ -10,6 +10,14 @@ + + + + + + + + diff --git a/tools/core/teavm-tooling.iml b/tools/core/teavm-tooling.iml index cfd432be7..b2ca91d0f 100644 --- a/tools/core/teavm-tooling.iml +++ b/tools/core/teavm-tooling.iml @@ -7,7 +7,7 @@ - + diff --git a/tools/junit/teavm-junit.iml b/tools/junit/teavm-junit.iml index cbd5028f7..00635f400 100644 --- a/tools/junit/teavm-junit.iml +++ b/tools/junit/teavm-junit.iml @@ -11,6 +11,14 @@ + + + + + + + +