added support for method arguments (maybe incomplete, should add tests with arrays)

This commit is contained in:
Shautvast 2023-07-29 00:30:41 +02:00
parent 93cd1a081a
commit 89d20eec57
6 changed files with 133 additions and 21 deletions

View file

@ -21,13 +21,27 @@ public class InvokerFactory {
public static final String SUPER = Java.internalName(AbstractInvoker.class);
public static Result<AbstractInvoker> of(MetaMethod m) {
ClassNode classNode = ASM.createDefaultClassNode("Invoker" + UUID.randomUUID(), SUPER);
ClassNode classNode = ASM.createDefaultClassNode("Invoker" + m.getName() + m.getDescriptor().replaceAll("[()/;\\[]", ""), SUPER);
MethodNode invokerMethod = new MethodNode(ACC_PUBLIC,
"invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
invokerMethod.instructions.add(new VarInsnNode(ALOAD, 1));
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getMetaClass().getRawName()));
for (int i = 0; i < m.getParameters().size(); i++) {
invokerMethod.instructions.add(new VarInsnNode(ALOAD, 2));
if (i < 6) {
invokerMethod.instructions.add(new InsnNode(3 + i)); //ICONST_X
} else {
invokerMethod.instructions.add(new LdcInsnNode(i));
}
invokerMethod.instructions.add(new InsnNode(AALOAD));
invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getParameters().get(i).getDescriptor()));
}
;
invokerMethod.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, m.getMetaClass().getRawName(), m.getName(), m.getDescriptor()));
convertReturnTypeForPrimitives(m, invokerMethod);
invokerMethod.instructions.add(new InsnNode(ARETURN));
classNode.methods.add(invokerMethod);
@ -35,11 +49,11 @@ public class InvokerFactory {
classNode.accept(classWriter);
byte[] byteArray = classWriter.toByteArray();
try (FileOutputStream out = new FileOutputStream("C.class")) {
out.write(byteArray);
} catch (IOException e) {
e.printStackTrace();
}
// try (FileOutputStream out = new FileOutputStream("C.class")) {
// out.write(byteArray);
// } catch (IOException e) {
// e.printStackTrace();
// }
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray);
try {
@ -50,5 +64,37 @@ public class InvokerFactory {
}
}
private static void convertReturnTypeForPrimitives(MetaMethod m, MethodNode invokerMethod) {
switch (m.getReturnParameter().getDescriptor()) {
case "V":
invokerMethod.instructions.add(new InsnNode(ACONST_NULL));
break;
case "Z":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"));
break;
case "I":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"));
break;
case "J":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
break;
case "F":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
break;
case "D":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"));
break;
case "C":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"));
break;
case "S":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
break;
case "B":
invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
break;
}
}
}

View file

@ -3,8 +3,9 @@ package com.github.shautvast.reflective;
import com.github.shautvast.reflective.java.Java;
import com.github.shautvast.rusty.Result;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import static com.github.shautvast.rusty.Result.err;
@ -15,16 +16,21 @@ public class MetaMethod {
private final String name;
private final int modifiers;
private final String descriptor;
private List<Class<?>> parameters = new LinkedList<>();
private Class<?> returnParameter;
private final AbstractInvoker invoker = InvokerFactory.of(this).unwrapOr(() -> null);
private final List<Parameter<?>> parameters;
private Parameter<?> returnParameter;
private final AbstractInvoker invoker;
public MetaMethod(MetaClass metaClass, String methodname, int modifiers, String descriptor) {
this.metaClass = metaClass;
this.name = methodname;
this.modifiers = modifiers;
this.descriptor = descriptor;
getParameters(descriptor);
this.parameters = createParameters(descriptor);
if (!Modifier.isPrivate(modifiers)) {
invoker = InvokerFactory.of(this).unwrap();//TODO
} else {
invoker = null;
}
}
public String getName() {
@ -43,11 +49,11 @@ public class MetaMethod {
return descriptor;
}
public List<Class<?>> getParameters() {
public List<Parameter<?>> getParameters() {
return parameters;
}
public Class<?> getReturnParameter() {
public Parameter<?> getReturnParameter() {
return returnParameter;
}
@ -65,24 +71,39 @@ public class MetaMethod {
}
}
private void getParameters(String descriptor) {
private List<Parameter<?>> createParameters(String descriptor) {
List<Parameter<?>> mutableParams = new ArrayList<>();
String[] split = descriptor.split("[()]");
String parms = split[1];
StringBuilder buf = new StringBuilder();
for (int i = 0; i < parms.length(); i++) {
String t = Character.toString(parms.charAt(i));
if (!"L".equals(t) && buf.length() == 0) {
this.parameters.add(Java.getClassFromDescriptor(t));
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(t), t));
}
if (";".equals(t)) {
buf.append(t);
this.parameters.add(Java.getClassFromDescriptor(buf.toString()));
String desc = buf.toString();
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(desc), correct(desc)));
buf = new StringBuilder();
} else {
buf.append(t);
}
}
this.parameters = Collections.unmodifiableList(this.parameters); //effectively final
this.returnParameter = Java.getClassFromDescriptor(split[2]);
String returnDesc = split[2];
this.returnParameter = new Parameter<>(
Java.getClassFromDescriptor(returnDesc),
correct(returnDesc));
return Collections.unmodifiableList(mutableParams);
}
private static String correct(String returnDesc) {
if (returnDesc.startsWith("L")) {
return returnDesc.substring(1, returnDesc.length() - 1);
} else {
return returnDesc;
}
}
}

View file

@ -0,0 +1,27 @@
package com.github.shautvast.reflective;
public class Parameter<T> {
private final Class<T> type;
private final String descriptor;
public Parameter(Class<T> type, String descriptor) {
this.type = type;
this.descriptor = descriptor;
}
public Class<T> getType() {
return type;
}
public boolean isVoid(){
return type == Void.class;
}
public boolean isPrimitive(){
return !descriptor.startsWith("L");
}
public String getDescriptor() {
return descriptor;
}
}

View file

@ -136,6 +136,8 @@ public class Java {
return isArray ? newInstance(short.class, new int[arrayDims]).getClass() : short.class;
case 'Z':
return isArray ? newInstance(boolean.class, new int[arrayDims]).getClass() : boolean.class;
case 'V':
return Void.class;
default:
throw new RuntimeException(new ClassNotFoundException("unknown descriptor: " + descriptor)); //must not happen
}

View file

@ -3,6 +3,7 @@ package com.github.shautvast.reflective;
public class DummyInvoker extends AbstractInvoker {
public Object invoke(Object instance, Object... arguments) {
return ((ReflectiveTest.Dummy) instance).getName();
((ReflectiveTest.Dummy) instance).setName((String)arguments[0]);
return null;
}
}

View file

@ -50,7 +50,7 @@ public class ReflectiveTest {
@Test
void testInvocation() {
void testInvokeGetter() {
Dummy dummy = new Dummy("bar");
MetaMethod getName = Reflective.getMetaForClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
@ -58,9 +58,20 @@ public class ReflectiveTest {
assertEquals("bar", getName.invoke(dummy).unwrap());
}
@Test
void testInvokeSetter() {
Dummy dummy = new Dummy("bar");
MetaClass metaForClass = Reflective.getMetaForClass(dummy.getClass());
MetaMethod setName = metaForClass.getMethod("setName").orElseGet(Assertions::fail);
assertEquals("bar", dummy.getName()); // before invoke
setName.invoke(dummy, "foo");
assertEquals("foo", dummy.getName()); // after invoke
}
public static class Dummy {
private final String name;
private String name;
public Dummy(String name) {
this.name = name;
@ -70,6 +81,10 @@ public class ReflectiveTest {
return privateMethod()[0];
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Dummy;