completed the ArrayFactory

This commit is contained in:
Shautvast 2023-09-02 13:34:02 +02:00
parent 7ccf137898
commit 31f2b7c0f5
12 changed files with 198 additions and 164 deletions

View file

@ -7,6 +7,8 @@ import com.github.shautvast.rusty.Result;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import static com.github.shautvast.reflective.java.Java.DOUBLE; import static com.github.shautvast.reflective.java.Java.DOUBLE;
@ -58,7 +60,9 @@ public class InvokerFactory {
} }
// put argument on the stack // put argument on the stack
insns.add(new InsnNode(AALOAD)); 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); unwrapPrimitive(method.getParameters().get(i), insns);
} }
// call the method // call the method
@ -76,11 +80,11 @@ public class InvokerFactory {
classNode.accept(classWriter); classNode.accept(classWriter);
byte[] byteArray = classWriter.toByteArray(); byte[] byteArray = classWriter.toByteArray();
// try (FileOutputStream out = new FileOutputStream("C"+method.getName() + ".class")) { try (FileOutputStream out = new FileOutputStream(method.getMetaClass().getName() + "_" + method.getName() + ".class")) {
// out.write(byteArray); out.write(byteArray);
// } catch (IOException e) { } catch (IOException e) {
// e.printStackTrace(); e.printStackTrace();
// } }
// load it into the JVM // load it into the JVM
ByteClassLoader.INSTANCE.addClass(className, byteArray); ByteClassLoader.INSTANCE.addClass(className, byteArray);
@ -160,6 +164,7 @@ public class InvokerFactory {
} }
private static Class<?> mapPrimitiveToWrapper(Class<?> type) { private static Class<?> mapPrimitiveToWrapper(Class<?> type) {
System.out.println(type);
if (type == int.class) { if (type == int.class) {
return Integer.class; return Integer.class;
} else if (type == byte.class) { } else if (type == byte.class) {

View file

@ -44,7 +44,7 @@ public class MetaClass {
} }
void addField(int access, String name, String descriptor) { 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) { public void addMethod(int access, String name, String descriptor) {
@ -62,4 +62,6 @@ public class MetaClass {
public String getRawName() { public String getRawName() {
return rawName; return rawName;
} }
} }

View file

@ -5,9 +5,12 @@ public class MetaField {
private final String name; private final String name;
private final int modifiers; 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.name = name;
this.modifiers = modifiers; this.modifiers = modifiers;
this.type = type;
} }
public String getName() { public String getName() {

View file

@ -78,13 +78,13 @@ public class MetaMethod {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
for (int i = 0; i < parms.length(); i++) { for (int i = 0; i < parms.length(); i++) {
String t = Character.toString(parms.charAt(i)); String t = Character.toString(parms.charAt(i));
if (!"L".equals(t) && buf.length() == 0) { if (!"[".equals(t) && !"L".equals(t) && buf.length() == 0) {
mutableParams.add(new Parameter<>(Java.getClassFromDescriptor(t), t)); mutableParams.add(new Parameter<>(Java.toClass(t), t));
} }
if (";".equals(t)) { if (";".equals(t)) {
buf.append(t); buf.append(t);
String desc = buf.toString(); 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(); buf = new StringBuilder();
} else { } else {
buf.append(t); buf.append(t);
@ -93,7 +93,7 @@ public class MetaMethod {
String returnDesc = split[2]; String returnDesc = split[2];
this.returnParameter = new Parameter<>( this.returnParameter = new Parameter<>(
Java.getClassFromDescriptor(returnDesc), Java.toClass(returnDesc),
correct(returnDesc)); correct(returnDesc));
return Collections.unmodifiableList(mutableParams); return Collections.unmodifiableList(mutableParams);

View file

@ -5,6 +5,9 @@ public class Parameter<T> {
private final String descriptor; private final String descriptor;
public Parameter(Class<T> type, String descriptor) { public Parameter(Class<T> type, String descriptor) {
if (type ==null){
throw new IllegalArgumentException("Type cannot be null");
}
this.type = type; this.type = type;
this.descriptor = descriptor; this.descriptor = descriptor;
} }
@ -17,10 +20,6 @@ public class Parameter<T> {
return type == Void.class; return type == Void.class;
} }
public boolean isPrimitive(){
return !descriptor.startsWith("L");
}
public String getDescriptor() { public String getDescriptor() {
return descriptor; return descriptor;
} }

View file

@ -6,70 +6,90 @@ import com.github.shautvast.reflective.java.Java;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
import java.io.FileOutputStream; import java.util.Arrays;
import java.io.IOException; 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 { public class ArrayFactory {
/* private static final ConcurrentMap<String, ArrayCreator> cache = new ConcurrentHashMap<>();
should become
public static <T> T newArray(Class<T> componentType) /**
or * Creates a new array of the specified type and dimensions
public static <T> T newArray(Class<T> componentType, int... 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) { public static Object newArray(Class<?> elementType, int... dimensions) {
return newArray(componentType.getName(), 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) { private static String syntheticClassName(String elementTypeName, int[] dimensions) {
ClassNode classNode = ASM.createDefaultClassNode( return "RAC" + elementTypeName
"ReflectiveCreator" + dimensions.length, .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)); Java.internalName(ArrayCreator.class));
MethodNode methodNode = new MethodNode(ACC_PUBLIC, MethodNode methodNode = new MethodNode(ACC_PUBLIC,
"newInstance", "()Ljava/lang/Object;", null, null); "newInstance", "()Ljava/lang/Object;", null, null);
classNode.methods.add(methodNode); classNode.methods.add(methodNode);
InsnList insns = methodNode.instructions; InsnList insns = methodNode.instructions;
Arrays.stream(dimensions).forEach(d -> insns.add(new LdcInsnNode(d)));
int localVarIndex = 0; insns.add(new MultiANewArrayInsnNode(createArrayType(componentType, dimensions), dimensions.length));
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));
insns.add(new InsnNode(ARETURN)); insns.add(new InsnNode(ARETURN));
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); return classNode;
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 private static String createArrayType(String componentType, int[] dimensions) {
ByteClassLoader.INSTANCE.addClass(classNode.name, byteArray); StringBuilder s = new StringBuilder();
try { s.append("[".repeat(dimensions.length));
return ((ArrayCreator) ByteClassLoader.INSTANCE.loadClass(classNode.name).getConstructor().newInstance()).newInstance(); boolean isObject = !componentType.startsWith("[") && componentType.contains("/");
} catch (Exception e) { if (isObject) {
throw new RuntimeException(e); s.append("L");
s.append(componentType);
} else {
s.append(Java.mapPrimitiveOrArrayName(componentType));
} }
if (isObject) {
s.append(";");
} }
return s.toString();
}
} }

View file

@ -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];
}
}

View file

@ -23,7 +23,7 @@ public class ByteClassLoader extends ClassLoader {
} }
public void addClass(String name, byte[] bytecode) { 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); classes.put(name, classDef);
} }

View file

@ -4,8 +4,6 @@ import org.objectweb.asm.ClassReader;
import java.io.IOException; import java.io.IOException;
import static java.lang.reflect.Array.newInstance;
/* /*
* common utils not for external use * common utils not for external use
*/ */
@ -107,48 +105,83 @@ public class Java {
throw new RuntimeException(); throw new RuntimeException();
} }
public static Class<?> getClassFromDescriptor(String descriptor) { public static Class<?> toClass(String descriptor) {
int arrayDims = 0; System.out.println("desc;" + descriptor);
while (descriptor.startsWith("[")) {
arrayDims++;
descriptor = descriptor.substring(1);
} //could be cheaper
if (descriptor.startsWith("L") && descriptor.endsWith(";")) {
try { try {
String className = descriptor.substring(1, descriptor.length() - 1).replaceAll("/", "."); if (descriptor.length() == 1) {
Class<?> clazz = Class.forName(className); return mapIfPrimitive(descriptor.charAt(0));
if (arrayDims > 0) {
clazz = newInstance(clazz, new int[arrayDims]).getClass();
} }
return clazz; 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) { } catch (ClassNotFoundException e) {
throw new RuntimeException(e); // not supposed to happen throw new RuntimeException(e);
} }
} else { }
char typeChar = descriptor.charAt(0);
boolean isArray = arrayDims != 0; private static Class<?> mapIfPrimitive(char descriptor) {
switch (typeChar) { switch (descriptor) {
case 'B': case 'B':
return isArray ? newInstance(byte.class, new int[arrayDims]).getClass() : byte.class; return 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': case 'S':
return isArray ? newInstance(short.class, new int[arrayDims]).getClass() : short.class; 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': case 'Z':
return isArray ? newInstance(boolean.class, new int[arrayDims]).getClass() : boolean.class; return boolean.class;
case 'V': case 'V':
return Void.class; return Void.class;
default: default:
throw new RuntimeException(new ClassNotFoundException("unknown descriptor: " + descriptor)); //must not happen 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 + "?");
} }
} }
} }

View file

@ -4,7 +4,7 @@ package com.github.shautvast.reflective;
public class DummyInvoker extends AbstractInvoker { public class DummyInvoker extends AbstractInvoker {
public Object invoke(Object instance, Object... arguments) { public Object invoke(Object instance, Object... arguments) {
((ReflectiveTest.Dummy) instance).setIntValue((int)arguments[0]); ((ReflectiveTest.Dummy) instance).setByteArrayValue((byte[][])arguments[0]);
return null; return null;
} }
} }

View file

@ -18,7 +18,7 @@ public class ReflectiveTest {
@Test @Test
void testMethods() { 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()); MetaClass metaDummy = Reflective.getMetaClass(dummy.getClass());
assertEquals("com.github.shautvast.reflective.ReflectiveTest$Dummy", metaDummy.getName()); assertEquals("com.github.shautvast.reflective.ReflectiveTest$Dummy", metaDummy.getName());
@ -49,7 +49,7 @@ public class ReflectiveTest {
@Test @Test
void testInvokeGetter() { 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); MetaMethod getStringValue = Reflective.getMetaClass(dummy.getClass()).getMethod("getStringValue").orElseGet(Assertions::fail);
// passing "foo" as the instance is not allowed // passing "foo" as the instance is not allowed
@ -60,7 +60,7 @@ public class ReflectiveTest {
@Test @Test
void testInvokeSetters() { 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()); MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
MetaMethod setByte = metaForClass.getMethod("setByteValue").orElseGet(Assertions::fail); MetaMethod setByte = metaForClass.getMethod("setByteValue").orElseGet(Assertions::fail);
@ -102,7 +102,7 @@ public class ReflectiveTest {
@Test @Test
void testInvocationExceptionHappened() { 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()); MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail); MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail);
@ -110,6 +110,13 @@ public class ReflectiveTest {
assertFalse(result.isOk()); 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 @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
@ -123,6 +130,8 @@ public class ReflectiveTest {
private char charValue; private char charValue;
private boolean booleanValue; private boolean booleanValue;
private String stringValue; private String stringValue;
private byte[][] byteArrayValue;
private String[] stringArrayValue;
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {

View file

@ -2,50 +2,30 @@ package com.github.shautvast.reflective.array;
import org.junit.jupiter.api.Test; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class ArraysTest { public class ArraysTest {
@Test @Test
void test1Dim() { void test1Dim() {
Object o = ArrayFactory.newArray(String.class, 1); Object o = ArrayFactory.newArray(String.class, 1);
// assertTrue(o instanceof String[]); assertTrue(o instanceof String[]);
} }
@Test @Test
void test2Dims() { void test2Dims() {
Object o = ArrayFactory.newArray(String.class, 1, 2); int[][] ints = (int[][])ArrayFactory.newArray(int[].class, 1);
// assertTrue(o instanceof String[][]); assertEquals(1, ints.length);
ints[0] = new int[1]; // will fail if array is not correctly created
assertEquals(1, ints[0].length);
} }
@Test @Test
void test3Dims() { void test3Dims() {
String[][][] array = (String[][][]) ArrayFactory.newArray(String[][][].class, 1, 2, 3); String[][][] array = (String[][][]) ArrayFactory.newArray(String.class, 6, 7, 8);
assertEquals(1, array.length); assertEquals(6, array.length);
array[0] = new String[2][1]; assertEquals(7, array[0].length);
array[0][1] = new String[1]; 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;");
}
} }