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.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) {

View file

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

View file

@ -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() {

View file

@ -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);

View file

@ -5,6 +5,9 @@ public class Parameter<T> {
private final String descriptor;
public Parameter(Class<T> 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<T> {
return type == Void.class;
}
public boolean isPrimitive(){
return !descriptor.startsWith("L");
}
public String getDescriptor() {
return descriptor;
}

View file

@ -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> T newArray(Class<T> componentType)
or
public static <T> T newArray(Class<T> componentType, int... dimensions)
private static final ConcurrentMap<String, ArrayCreator> 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();
}
}

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) {
Class<?> classDef = defineClass(name, bytecode, 0, bytecode.length);
Class<?> classDef = defineClass(null, bytecode, 0, bytecode.length);
classes.put(name, classDef);
}

View file

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

View file

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

View file

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

View file

@ -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;");
}
}