From 3e636667a95c047057de4d7086311a45de314c48 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sat, 29 Jul 2023 16:10:41 +0200 Subject: [PATCH] test fixes, javadoc --- .../shautvast/reflective/InvokerFactory.java | 99 ++++++++++++------- .../reflective/java/ByteClassLoader.java | 4 + .../shautvast/reflective/java/Java.java | 8 ++ .../shautvast/reflective/ReflectiveTest.java | 11 +-- 4 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java index 2cc2d5f..e37e2fe 100644 --- a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java +++ b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java @@ -7,11 +7,13 @@ import com.github.shautvast.rusty.Result; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.*; -import java.io.FileOutputStream; -import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.util.UUID; +import static com.github.shautvast.reflective.java.Java.*; +import static com.github.shautvast.reflective.java.Java.DOUBLE; +import static com.github.shautvast.reflective.java.Java.FLOAT; +import static com.github.shautvast.reflective.java.Java.INTEGER; +import static com.github.shautvast.reflective.java.Java.LONG; import static com.github.shautvast.rusty.Result.err; import static com.github.shautvast.rusty.Result.ok; import static org.objectweb.asm.Opcodes.*; @@ -20,78 +22,105 @@ public class InvokerFactory { public static final String SUPER = Java.internalName(AbstractInvoker.class); - public static Result of(MetaMethod m) { - ClassNode classNode = ASM.createDefaultClassNode("Invoker" + m.getName() + m.getDescriptor().replaceAll("[()/;\\[]", ""), SUPER); + public static final String VALUEOF = "valueOf"; + /** + * Creates an invoker for a single method + * + * @param method the MetaMethod representing the method to invoke + * @return a generated instance of an AbstractInvoker + */ + public static Result of(MetaMethod method) { + // new ASM ClassNode with default constructor + String className = "Invoker" + method.getName() + method.getDescriptor().replaceAll("[()/;\\[]", ""); + if (ByteClassLoader.INSTANCE.isDefined(className)) { + return getInvoker(className); + } + + ClassNode classNode = ASM.createDefaultClassNode(className, SUPER); + + // the invoker method 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)); + // local var #1 is the class to invoke the method on + InsnList insns = invokerMethod.instructions; + insns.add(new VarInsnNode(ALOAD, 1)); + // it comes in as Object and has to be cast to it's actual type + insns.add(new TypeInsnNode(CHECKCAST, method.getMetaClass().getRawName())); + + // the arguments are passed as varargs / array of Object + for (int i = 0; i < method.getParameters().size(); i++) { + insns.add(new VarInsnNode(ALOAD, 2)); if (i < 6) { - invokerMethod.instructions.add(new InsnNode(3 + i)); //ICONST_X + insns.add(new InsnNode(3 + i)); //ICONST_X } else { - invokerMethod.instructions.add(new LdcInsnNode(i)); + insns.add(new LdcInsnNode(i)); } - invokerMethod.instructions.add(new InsnNode(AALOAD)); - invokerMethod.instructions.add(new TypeInsnNode(CHECKCAST, m.getParameters().get(i).getDescriptor())); + // put argument on the stack + insns.add(new InsnNode(AALOAD)); + insns.add(new TypeInsnNode(CHECKCAST, method.getParameters().get(i).getDescriptor())); } - ; - invokerMethod.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, m.getMetaClass().getRawName(), m.getName(), m.getDescriptor())); + // call the method + insns.add(new MethodInsnNode(INVOKEVIRTUAL, method.getMetaClass().getRawName(), method.getName(), method.getDescriptor())); + // if the returned argument is primitive, wrap it as an Object + wrapAnyPrimitivesForReturnObject(method, invokerMethod); + // return the object on the stack + insns.add(new InsnNode(ARETURN)); - convertReturnTypeForPrimitives(m, invokerMethod); - - invokerMethod.instructions.add(new InsnNode(ARETURN)); + // add the method to the class classNode.methods.add(invokerMethod); + // create the bytecode ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); classNode.accept(classWriter); byte[] byteArray = classWriter.toByteArray(); -// try (FileOutputStream out = new FileOutputStream("C.class")) { -// out.write(byteArray); -// } catch (IOException e) { -// e.printStackTrace(); -// } + // load it into the JVM + ByteClassLoader.INSTANCE.addClass(className, byteArray); + return getInvoker(classNode.name); + } - ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray); + private static Result getInvoker(String name) { try { - return ok((AbstractInvoker) ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance()); + return ok((AbstractInvoker) ByteClassLoader.INSTANCE.loadClass(name).getConstructor().newInstance()); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { return err(e); } } - private static void convertReturnTypeForPrimitives(MetaMethod m, MethodNode invokerMethod) { + /* + * wrap primitives in their wrapper + */ + private static void wrapAnyPrimitivesForReturnObject(MetaMethod m, MethodNode invokerMethod) { + InsnList insns = invokerMethod.instructions; switch (m.getReturnParameter().getDescriptor()) { case "V": - invokerMethod.instructions.add(new InsnNode(ACONST_NULL)); + insns.add(new InsnNode(ACONST_NULL)); break; case "Z": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;")); + insns.add(new MethodInsnNode(INVOKESTATIC, BOOLEAN, VALUEOF, "(Z)Ljava/lang/Boolean;")); break; case "I": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;")); + insns.add(new MethodInsnNode(INVOKESTATIC, INTEGER, VALUEOF, "(I)Ljava/lang/Integer;")); break; case "J": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;")); + insns.add(new MethodInsnNode(INVOKESTATIC, LONG, VALUEOF, "(J)Ljava/lang/Long;")); break; case "F": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;")); + insns.add(new MethodInsnNode(INVOKESTATIC, FLOAT, VALUEOF, "(F)Ljava/lang/Float;")); break; case "D": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;")); + insns.add(new MethodInsnNode(INVOKESTATIC, DOUBLE, VALUEOF, "(D)Ljava/lang/Double;")); break; case "C": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;")); + insns.add(new MethodInsnNode(INVOKESTATIC, CHAR, VALUEOF, "(C)Ljava/lang/Character;")); break; case "S": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;")); + insns.add(new MethodInsnNode(INVOKESTATIC, SHORT, VALUEOF, "(S)Ljava/lang/Short;")); break; case "B": - invokerMethod.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;")); + insns.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", VALUEOF, "(B)Ljava/lang/Byte;")); break; } } diff --git a/src/main/java/com/github/shautvast/reflective/java/ByteClassLoader.java b/src/main/java/com/github/shautvast/reflective/java/ByteClassLoader.java index 3e733db..f5d7015 100644 --- a/src/main/java/com/github/shautvast/reflective/java/ByteClassLoader.java +++ b/src/main/java/com/github/shautvast/reflective/java/ByteClassLoader.java @@ -26,4 +26,8 @@ public class ByteClassLoader extends ClassLoader { Class classDef = defineClass(name, bytecode, 0, bytecode.length); classes.put(name, classDef); } + + public boolean isDefined(String className) { + return classes.containsKey(className); + } } diff --git a/src/main/java/com/github/shautvast/reflective/java/Java.java b/src/main/java/com/github/shautvast/reflective/java/Java.java index 53109d6..16c9047 100644 --- a/src/main/java/com/github/shautvast/reflective/java/Java.java +++ b/src/main/java/com/github/shautvast/reflective/java/Java.java @@ -15,6 +15,14 @@ public class Java { public static final String ZERO_ARGS_VOID = "()V"; public static final String OBJECT = "Ljava/lang/Object;"; + public static final String BOOLEAN = "java/lang/Boolean"; + public static final String INTEGER = "java/lang/Integer"; + public static final String LONG = "java/lang/Long"; + public static final String FLOAT = "java/lang/Float"; + public static final String DOUBLE = "java/lang/Double"; + public static final String CHAR = "java/lang/Character"; + public static final String SHORT = "java/lang/Short"; + public static String internalName(String className) { return className.replaceAll("\\.", "/"); } diff --git a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java index 1bc8945..429c4b7 100644 --- a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java +++ b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java @@ -25,26 +25,25 @@ public class ReflectiveTest { Set methods = metaDummy.getMethods(); assertFalse(methods.isEmpty()); - assertEquals(4, methods.size()); + assertEquals(5, methods.size()); MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail); - assertEquals(List.of(Object.class), equals.getParameters()); - assertEquals(boolean.class, equals.getReturnParameter()); + assertEquals(boolean.class, equals.getReturnParameter().getType()); assertTrue(Modifier.isPublic(equals.getModifiers())); MetaMethod hashCode = metaDummy.getMethod("hashCode").orElseGet(Assertions::fail); assertEquals(List.of(), hashCode.getParameters()); - assertEquals(int.class, hashCode.getReturnParameter()); + assertEquals(int.class, hashCode.getReturnParameter().getType()); assertTrue(Modifier.isPublic(hashCode.getModifiers())); MetaMethod getName = metaDummy.getMethod("getName").orElseGet(Assertions::fail); assertEquals(List.of(), getName.getParameters()); - assertEquals(String.class, getName.getReturnParameter()); + assertEquals(String.class, getName.getReturnParameter().getType()); assertTrue(Modifier.isPublic(getName.getModifiers())); MetaMethod privateMethod = metaDummy.getMethod("privateMethod").orElseGet(Assertions::fail); assertEquals(List.of(), privateMethod.getParameters()); - assertEquals(String[].class, privateMethod.getReturnParameter()); + assertEquals(String[].class, privateMethod.getReturnParameter().getType()); assertTrue(Modifier.isPrivate(privateMethod.getModifiers())); }