From 31f2b7c0f575d081ff8d0a2df8e99016b6d05929 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sat, 2 Sep 2023 13:34:02 +0200 Subject: [PATCH] completed the ArrayFactory --- .../shautvast/reflective/InvokerFactory.java | 17 ++- .../shautvast/reflective/MetaClass.java | 4 +- .../shautvast/reflective/MetaField.java | 5 +- .../shautvast/reflective/MetaMethod.java | 8 +- .../shautvast/reflective/Parameter.java | 7 +- .../reflective/array/ArrayFactory.java | 124 ++++++++++-------- .../shautvast/reflective/array/Arrays.java | 17 --- .../reflective/java/ByteClassLoader.java | 2 +- .../shautvast/reflective/java/Java.java | 119 +++++++++++------ .../shautvast/reflective/DummyInvoker.java | 2 +- .../shautvast/reflective/ReflectiveTest.java | 19 ++- .../reflective/array/ArraysTest.java | 38 ++---- 12 files changed, 198 insertions(+), 164 deletions(-) delete mode 100644 src/main/java/com/github/shautvast/reflective/array/Arrays.java diff --git a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java index 1d171c8..7e1f44e 100644 --- a/src/main/java/com/github/shautvast/reflective/InvokerFactory.java +++ b/src/main/java/com/github/shautvast/reflective/InvokerFactory.java @@ -7,6 +7,8 @@ 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 static com.github.shautvast.reflective.java.Java.DOUBLE; @@ -58,7 +60,9 @@ public class InvokerFactory { } // put argument on the stack insns.add(new InsnNode(AALOAD)); - insns.add(new TypeInsnNode(CHECKCAST, Java.internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName()))); + String type = internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName()); + System.out.println("--" + type); + insns.add(new TypeInsnNode(CHECKCAST, type)); unwrapPrimitive(method.getParameters().get(i), insns); } // call the method @@ -76,11 +80,11 @@ public class InvokerFactory { classNode.accept(classWriter); byte[] byteArray = classWriter.toByteArray(); -// try (FileOutputStream out = new FileOutputStream("C"+method.getName() + ".class")) { -// out.write(byteArray); -// } catch (IOException e) { -// e.printStackTrace(); -// } + try (FileOutputStream out = new FileOutputStream(method.getMetaClass().getName() + "_" + method.getName() + ".class")) { + out.write(byteArray); + } catch (IOException e) { + e.printStackTrace(); + } // load it into the JVM ByteClassLoader.INSTANCE.addClass(className, byteArray); @@ -160,6 +164,7 @@ public class InvokerFactory { } private static Class mapPrimitiveToWrapper(Class type) { + System.out.println(type); if (type == int.class) { return Integer.class; } else if (type == byte.class) { diff --git a/src/main/java/com/github/shautvast/reflective/MetaClass.java b/src/main/java/com/github/shautvast/reflective/MetaClass.java index 0dfd467..d883d8f 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaClass.java +++ b/src/main/java/com/github/shautvast/reflective/MetaClass.java @@ -44,7 +44,7 @@ public class MetaClass { } void addField(int access, String name, String descriptor) { - fields.put(name, new MetaField(name, access)); //ASM access same as reflect modifiers? + fields.put(name, new MetaField(name, access, Java.toClass(descriptor))); //ASM access same as reflect modifiers? } public void addMethod(int access, String name, String descriptor) { @@ -62,4 +62,6 @@ public class MetaClass { public String getRawName() { return rawName; } + + } diff --git a/src/main/java/com/github/shautvast/reflective/MetaField.java b/src/main/java/com/github/shautvast/reflective/MetaField.java index 9a05530..3b0342d 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaField.java +++ b/src/main/java/com/github/shautvast/reflective/MetaField.java @@ -5,9 +5,12 @@ public class MetaField { private final String name; private final int modifiers; - public MetaField(String name, int modifiers) { + private final Class type; + + public MetaField(String name, int modifiers, Class type) { this.name = name; this.modifiers = modifiers; + this.type = type; } public String getName() { diff --git a/src/main/java/com/github/shautvast/reflective/MetaMethod.java b/src/main/java/com/github/shautvast/reflective/MetaMethod.java index b3d7f9c..9626437 100644 --- a/src/main/java/com/github/shautvast/reflective/MetaMethod.java +++ b/src/main/java/com/github/shautvast/reflective/MetaMethod.java @@ -78,13 +78,13 @@ public class MetaMethod { 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) { - mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(t), t)); + if (!"[".equals(t) && !"L".equals(t) && buf.length() == 0) { + mutableParams.add(new Parameter<>(Java.toClass(t), t)); } if (";".equals(t)) { buf.append(t); String desc = buf.toString(); - mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(desc), correct(desc))); + mutableParams.add(new Parameter<>(Java.toClass(desc), correct(desc))); buf = new StringBuilder(); } else { buf.append(t); @@ -93,7 +93,7 @@ public class MetaMethod { String returnDesc = split[2]; this.returnParameter = new Parameter<>( - Java.getClassFromDescriptor(returnDesc), + Java.toClass(returnDesc), correct(returnDesc)); return Collections.unmodifiableList(mutableParams); diff --git a/src/main/java/com/github/shautvast/reflective/Parameter.java b/src/main/java/com/github/shautvast/reflective/Parameter.java index 85408f7..6218d4b 100644 --- a/src/main/java/com/github/shautvast/reflective/Parameter.java +++ b/src/main/java/com/github/shautvast/reflective/Parameter.java @@ -5,6 +5,9 @@ public class Parameter { private final String descriptor; public Parameter(Class type, String descriptor) { + if (type ==null){ + throw new IllegalArgumentException("Type cannot be null"); + } this.type = type; this.descriptor = descriptor; } @@ -17,10 +20,6 @@ public class Parameter { return type == Void.class; } - public boolean isPrimitive(){ - return !descriptor.startsWith("L"); - } - public String getDescriptor() { return descriptor; } diff --git a/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java b/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java index 0e4fb46..e1d2416 100644 --- a/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java +++ b/src/main/java/com/github/shautvast/reflective/array/ArrayFactory.java @@ -6,70 +6,90 @@ import com.github.shautvast.reflective.java.Java; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.*; -import java.io.FileOutputStream; -import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; -import static org.objectweb.asm.Opcodes.*; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ARETURN; +/** + * Factory class for dynamically creating arrays + */ public class ArrayFactory { - /* - should become - public static T newArray(Class componentType) - or - public static T newArray(Class componentType, int... dimensions) + private static final ConcurrentMap cache = new ConcurrentHashMap<>(); + + /** + * Creates a new array of the specified type and dimensions + * + * @param elementType the array element type + * @param dimensions array of ints, ie {10} means 1 dimension, length 10. {10,20} means 2 dimensions, size 10 by 20 + * @return an object that you can cast to the expected array type */ - public static Object newArray(Class componentType, int... dimensions) { - return newArray(componentType.getName(), dimensions); + public static Object newArray(Class elementType, int... dimensions) { + String elementTypeName = Java.internalName(elementType); + String syntheticClassName = syntheticClassName(elementTypeName, dimensions); + + return cache.computeIfAbsent(syntheticClassName, + k -> createSyntheticArrayCreator(elementTypeName, syntheticClassName, dimensions)).newInstance(); } - public static Object newArray(String componentType, int... dimensions) { - ClassNode classNode = ASM.createDefaultClassNode( - "ReflectiveCreator" + dimensions.length, + private static String syntheticClassName(String elementTypeName, int[] dimensions) { + return "RAC" + elementTypeName + .replaceAll("[/.\\[;]", "") + .toLowerCase() + "L" + dimensions.length; + } + + private static ArrayCreator createSyntheticArrayCreator(String componentType, String name, int... dimensions) { + ClassNode classNode = createASMClassNode(componentType, name, dimensions); + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + classNode.accept(classWriter); + + byte[] byteArray = classWriter.toByteArray(); + +// try (FileOutputStream out = new FileOutputStream(name + ".class")) { +// out.write(byteArray); +// } catch (IOException e) { +// e.printStackTrace(); +// } + + ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray); + + try { + return (ArrayCreator) (ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static ClassNode createASMClassNode(String componentType, String name, int[] dimensions) { + ClassNode classNode = ASM.createDefaultClassNode(name, Java.internalName(ArrayCreator.class)); MethodNode methodNode = new MethodNode(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null); classNode.methods.add(methodNode); InsnList insns = methodNode.instructions; - - int localVarIndex = 0; - StringBuilder arrayType = new StringBuilder(dimensions.length); - String post = ""; - String pre = ""; - insns.add(new LdcInsnNode(1)); - insns.add(new LdcInsnNode(1)); - insns.add(new LdcInsnNode(1)); - insns.add(new MultiANewArrayInsnNode("[[[Ljava/lang/String;",3)); - -// for (int dim : dimensions) { -// insns.add(new LdcInsnNode(dim)); -// String type = arrayType + pre + Java.internalName(componentType) + post; -// insns.add(new TypeInsnNode(ANEWARRAY, type)); -// insns.add(new VarInsnNode(ASTORE, ++localVarIndex)); -// arrayType.append("["); -// pre = "L"; -// post = ";"; -// -// } - -// insns.add(new VarInsnNode(ALOAD, localVarIndex)); + Arrays.stream(dimensions).forEach(d -> insns.add(new LdcInsnNode(d))); + insns.add(new MultiANewArrayInsnNode(createArrayType(componentType, dimensions), dimensions.length)); insns.add(new InsnNode(ARETURN)); - ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - classNode.accept(classWriter); - byte[] byteArray = classWriter.toByteArray(); - - try (FileOutputStream out = new FileOutputStream("A.class")) { - out.write(byteArray); - } catch (IOException e) { - e.printStackTrace(); - } - - // load it into the JVM - ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray); - try { - return ((ArrayCreator) ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance()).newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } + return classNode; } + + private static String createArrayType(String componentType, int[] dimensions) { + StringBuilder s = new StringBuilder(); + s.append("[".repeat(dimensions.length)); + boolean isObject = !componentType.startsWith("[") && componentType.contains("/"); + if (isObject) { + s.append("L"); + s.append(componentType); + } else { + s.append(Java.mapPrimitiveOrArrayName(componentType)); + } + if (isObject) { + s.append(";"); + } + return s.toString(); + } + } diff --git a/src/main/java/com/github/shautvast/reflective/array/Arrays.java b/src/main/java/com/github/shautvast/reflective/array/Arrays.java deleted file mode 100644 index 6060468..0000000 --- a/src/main/java/com/github/shautvast/reflective/array/Arrays.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.shautvast.reflective.array; - -public class Arrays { - private Arrays() { - } - - public static Object newInstance(Class componentType, int... dimensions) - throws IllegalArgumentException, NegativeArraySizeException { - return new String[dimensions[1]][2][3]; - } - - public static void main(String[] args) { - String[][][] a = (String[][][])newInstance(String.class, 1, 2, 3); - a[0]=new String[1][2]; - - } -} 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 f5d7015..8b085a5 100644 --- a/src/main/java/com/github/shautvast/reflective/java/ByteClassLoader.java +++ b/src/main/java/com/github/shautvast/reflective/java/ByteClassLoader.java @@ -23,7 +23,7 @@ public class ByteClassLoader extends ClassLoader { } public void addClass(String name, byte[] bytecode) { - Class classDef = defineClass(name, bytecode, 0, bytecode.length); + Class classDef = defineClass(null, bytecode, 0, bytecode.length); classes.put(name, classDef); } 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 d440b41..5fb57a0 100644 --- a/src/main/java/com/github/shautvast/reflective/java/Java.java +++ b/src/main/java/com/github/shautvast/reflective/java/Java.java @@ -4,8 +4,6 @@ import org.objectweb.asm.ClassReader; import java.io.IOException; -import static java.lang.reflect.Array.newInstance; - /* * common utils not for external use */ @@ -107,49 +105,84 @@ public class Java { throw new RuntimeException(); } - public static Class getClassFromDescriptor(String descriptor) { - int arrayDims = 0; - while (descriptor.startsWith("[")) { - arrayDims++; - descriptor = descriptor.substring(1); - } //could be cheaper + public static Class toClass(String descriptor) { + System.out.println("desc;" + descriptor); + try { + if (descriptor.length() == 1) { + return mapIfPrimitive(descriptor.charAt(0)); + } + if (!descriptor.startsWith("[") && descriptor.endsWith(";")) { + descriptor = descriptor.substring(0, descriptor.length() - 1); + } + int i = 0; + while (descriptor.charAt(i) == '[' && i < 256) { + i++; + } + assert i < 256; + String dims = descriptor.substring(0, i); + String type = descriptor.substring(i); + if (i == 0 && type.startsWith("L")) { + type = type.substring(1); + } + type = Java.externalName(type); + Class aClass = Class.forName(dims + type); + System.out.println(aClass); + return aClass; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } - if (descriptor.startsWith("L") && descriptor.endsWith(";")) { - try { - String className = descriptor.substring(1, descriptor.length() - 1).replaceAll("/", "."); - Class clazz = Class.forName(className); - if (arrayDims > 0) { - clazz = newInstance(clazz, new int[arrayDims]).getClass(); + private static Class mapIfPrimitive(char descriptor) { + switch (descriptor) { + case 'B': + return byte.class; + case 'S': + return short.class; + case 'I': + return int.class; + case 'J': + return long.class; + case 'F': + return float.class; + case 'D': + return double.class; + case 'C': + return char.class; + case 'Z': + return boolean.class; + case 'V': + return Void.class; + default: + System.out.println("desc:" + descriptor); + return null; + } + } + + public static String mapPrimitiveOrArrayName(String type) { + switch (type) { + case "byte": + return "B"; + case "short": + return "S"; + case "int": + return "I"; + case "long": + return "J"; + case "float": + return "F"; + case "double": + return "D"; + case "char": + return "C"; + case "boolean": + return "Z"; + default: + if (type.startsWith("[") || type.contains("/")) { + return type; + } else { + throw new IllegalArgumentException(type + "?"); } - return clazz; - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); // not supposed to happen - } - } else { - char typeChar = descriptor.charAt(0); - boolean isArray = arrayDims != 0; - switch (typeChar) { - case 'B': - return isArray ? newInstance(byte.class, new int[arrayDims]).getClass() : byte.class; - case 'C': - return isArray ? newInstance(char.class, new int[arrayDims]).getClass() : char.class; - case 'D': - return isArray ? newInstance(double.class, new int[arrayDims]).getClass() : double.class; - case 'F': - return isArray ? newInstance(float.class, new int[arrayDims]).getClass() : float.class; - case 'I': - return isArray ? newInstance(int.class, new int[arrayDims]).getClass() : int.class; - case 'J': - return isArray ? newInstance(long.class, new int[arrayDims]).getClass() : long.class; - case 'S': - 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 - } } } } diff --git a/src/test/java/com/github/shautvast/reflective/DummyInvoker.java b/src/test/java/com/github/shautvast/reflective/DummyInvoker.java index 072d33d..3589ebd 100644 --- a/src/test/java/com/github/shautvast/reflective/DummyInvoker.java +++ b/src/test/java/com/github/shautvast/reflective/DummyInvoker.java @@ -4,7 +4,7 @@ package com.github.shautvast.reflective; public class DummyInvoker extends AbstractInvoker { public Object invoke(Object instance, Object... arguments) { - ((ReflectiveTest.Dummy) instance).setIntValue((int)arguments[0]); + ((ReflectiveTest.Dummy) instance).setByteArrayValue((byte[][])arguments[0]); return null; } } diff --git a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java index 8604957..4155330 100644 --- a/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java +++ b/src/test/java/com/github/shautvast/reflective/ReflectiveTest.java @@ -18,7 +18,7 @@ public class ReflectiveTest { @Test void testMethods() { - Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!"); + Dummy dummy = getDummy(); MetaClass metaDummy = Reflective.getMetaClass(dummy.getClass()); assertEquals("com.github.shautvast.reflective.ReflectiveTest$Dummy", metaDummy.getName()); @@ -49,7 +49,7 @@ public class ReflectiveTest { @Test void testInvokeGetter() { - Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!"); + Dummy dummy = getDummy(); MetaMethod getStringValue = Reflective.getMetaClass(dummy.getClass()).getMethod("getStringValue").orElseGet(Assertions::fail); // passing "foo" as the instance is not allowed @@ -60,7 +60,7 @@ public class ReflectiveTest { @Test void testInvokeSetters() { - Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!"); + Dummy dummy = getDummy(); MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass()); MetaMethod setByte = metaForClass.getMethod("setByteValue").orElseGet(Assertions::fail); @@ -102,7 +102,7 @@ public class ReflectiveTest { @Test void testInvocationExceptionHappened() { - Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!"); + Dummy dummy = getDummy(); MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass()); MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail); @@ -110,6 +110,13 @@ public class ReflectiveTest { assertFalse(result.isOk()); } + private static Dummy getDummy() { + return new Dummy((byte) 42, (short) 43, + 44, 45, 46.0F, 47.0, + 'D', true, "don't panic!", + new byte[1][1], new String[]{"Please don't touch that button"}); + } + @Getter @Setter @AllArgsConstructor @@ -123,6 +130,8 @@ public class ReflectiveTest { private char charValue; private boolean booleanValue; private String stringValue; + private byte[][] byteArrayValue; + private String[] stringArrayValue; @Override public boolean equals(Object obj) { @@ -135,7 +144,7 @@ public class ReflectiveTest { } @SuppressWarnings("unused") - private String[] privateMethod(){ + private String[] privateMethod() { return new String[1]; } diff --git a/src/test/java/com/github/shautvast/reflective/array/ArraysTest.java b/src/test/java/com/github/shautvast/reflective/array/ArraysTest.java index ae16365..d11891b 100644 --- a/src/test/java/com/github/shautvast/reflective/array/ArraysTest.java +++ b/src/test/java/com/github/shautvast/reflective/array/ArraysTest.java @@ -2,50 +2,30 @@ package com.github.shautvast.reflective.array; import org.junit.jupiter.api.Test; -import java.lang.reflect.Array; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class ArraysTest { - @Test void test1Dim() { Object o = ArrayFactory.newArray(String.class, 1); -// assertTrue(o instanceof String[]); + assertTrue(o instanceof String[]); } @Test void test2Dims() { - Object o = ArrayFactory.newArray(String.class, 1, 2); -// assertTrue(o instanceof String[][]); + int[][] ints = (int[][])ArrayFactory.newArray(int[].class, 1); + assertEquals(1, ints.length); + ints[0] = new int[1]; // will fail if array is not correctly created + assertEquals(1, ints[0].length); } @Test void test3Dims() { - String[][][] array = (String[][][]) ArrayFactory.newArray(String[][][].class, 1, 2, 3); - assertEquals(1, array.length); - array[0] = new String[2][1]; - array[0][1] = new String[1]; + String[][][] array = (String[][][]) ArrayFactory.newArray(String.class, 6, 7, 8); + assertEquals(6, array.length); + assertEquals(7, array[0].length); + assertEquals(8, array[0][0].length); } - - @Test - void test3DimsT() { - String[][][] array = new String[1][1][1]; - assertEquals(1, array.length); - array[0] = new String[2][1]; - - array[0][0] = new String[1]; - array[0][1] = new String[1]; - array[0][0][0]="0,0,0"; - array[0][1][0]="0,1,0"; // WTF? - System.out.println(array[0][1][0]); - } - - @Test - void forName() throws ClassNotFoundException { - Class.forName("[Ljava.lang.String;"); - } - }