fixed bug with primitive arguments

This commit is contained in:
Shautvast 2023-08-22 17:54:13 +02:00
parent 1a09f3e687
commit 7de5019a4f
6 changed files with 141 additions and 52 deletions

View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.shautvast</groupId>
<artifactId>reflective</artifactId>
<version>1.0.0</version>
<version>1.1.0</version>
<name>reflective</name>
<description>Reflective utils that don't use java.lang.reflect</description>
@ -29,6 +29,12 @@
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>test</scope>
</dependency>
</dependencies>
<distributionManagement>

View file

@ -9,11 +9,11 @@ import org.objectweb.asm.tree.*;
import java.lang.reflect.InvocationTargetException;
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.reflective.java.Java.*;
import static com.github.shautvast.rusty.Result.err;
import static com.github.shautvast.rusty.Result.ok;
import static org.objectweb.asm.Opcodes.*;
@ -58,12 +58,13 @@ public class InvokerFactory {
}
// put argument on the stack
insns.add(new InsnNode(AALOAD));
insns.add(new TypeInsnNode(CHECKCAST, method.getParameters().get(i).getDescriptor()));
insns.add(new TypeInsnNode(CHECKCAST, Java.internalName(mapPrimitiveToWrapper(method.getParameters().get(i).getType()).getName())));
unwrapPrimitive(method.getParameters().get(i), insns);
}
// 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);
wrapPrimitive(method.getReturnParameter(), invokerMethod.instructions);
// return the object on the stack
insns.add(new InsnNode(ARETURN));
@ -75,7 +76,7 @@ public class InvokerFactory {
classNode.accept(classWriter);
byte[] byteArray = classWriter.toByteArray();
// try (FileOutputStream out = new FileOutputStream("C.class")) {
// try (FileOutputStream out = new FileOutputStream("C"+method.getName() + ".class")) {
// out.write(byteArray);
// } catch (IOException e) {
// e.printStackTrace();
@ -95,12 +96,39 @@ public class InvokerFactory {
}
}
private static void unwrapPrimitive(Parameter<?> parameter, InsnList insns) {
switch (parameter.getDescriptor()) {
case "B":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"));
break;
case "S":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"));
break;
case "I":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"));
break;
case "Z":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"));
break;
case "J":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"));
break;
case "F":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"));
break;
case "D":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"));
break;
case "C":
insns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"));
}
}
/*
* wrap primitives in their wrapper
*/
private static void wrapAnyPrimitivesForReturnObject(MetaMethod m, MethodNode invokerMethod) {
InsnList insns = invokerMethod.instructions;
switch (m.getReturnParameter().getDescriptor()) {
private static void wrapPrimitive(Parameter<?> parameter, InsnList insns) {
switch (parameter.getDescriptor()) {
case "V":
insns.add(new InsnNode(ACONST_NULL));
break;
@ -126,10 +154,32 @@ public class InvokerFactory {
insns.add(new MethodInsnNode(INVOKESTATIC, SHORT, VALUEOF, "(S)Ljava/lang/Short;"));
break;
case "B":
insns.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", VALUEOF, "(B)Ljava/lang/Byte;"));
insns.add(new MethodInsnNode(INVOKESTATIC, BYTE, VALUEOF, "(B)Ljava/lang/Byte;"));
break;
}
}
private static Class<?> mapPrimitiveToWrapper(Class<?> type) {
if (type == int.class) {
return Integer.class;
} else if (type == byte.class) {
return Byte.class;
} else if (type == short.class) {
return Short.class;
} else if (type == long.class) {
return Long.class;
} else if (type == float.class) {
return Float.class;
} else if (type == double.class) {
return Double.class;
} else if (type == char.class) {
return Character.class;
} else if (type == boolean.class) {
return Boolean.class;
} else {
return type;
}
}
}

View file

@ -34,6 +34,7 @@ public class MetaClass {
return Set.copyOf(methods.values());
}
// should account for overloading!
public Optional<MetaMethod> getMethod(String name) {
return Optional.ofNullable(methods.get(name));
}

View file

@ -22,6 +22,7 @@ public class Java {
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 final String BYTE = "java/lang/Byte";
public static String internalName(String className) {
return className.replaceAll("\\.", "/");

View file

@ -1,9 +1,10 @@
package com.github.shautvast.reflective;
@SuppressWarnings("ALL")
public class DummyInvoker extends AbstractInvoker {
public Object invoke(Object instance, Object... arguments) {
((ReflectiveTest.Dummy) instance).setName((String)arguments[0]);
((ReflectiveTest.Dummy) instance).setIntValue((int)arguments[0]);
return null;
}
}

View file

@ -2,11 +2,13 @@ package com.github.shautvast.reflective;
import com.github.shautvast.rusty.Panic;
import com.github.shautvast.rusty.Result;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@ -16,17 +18,13 @@ public class ReflectiveTest {
@Test
void testMethods() {
Dummy dummy = new Dummy("bar");
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
MetaClass metaDummy = Reflective.getMetaClass(dummy.getClass());
assertEquals("com.github.shautvast.reflective.ReflectiveTest$Dummy", metaDummy.getName());
Iterator<MetaField> fields = metaDummy.getFields().iterator();
assertTrue(fields.hasNext());
assertEquals("name", fields.next().getName());
Set<MetaMethod> methods = metaDummy.getMethods();
assertFalse(methods.isEmpty());
assertEquals(6, methods.size());
assertEquals(22, methods.size());
MetaMethod equals = metaDummy.getMethod("equals").orElseGet(Assertions::fail);
assertEquals(boolean.class, equals.getReturnParameter().getType());
@ -37,10 +35,10 @@ public class ReflectiveTest {
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().getType());
assertTrue(Modifier.isPublic(getName.getModifiers()));
MetaMethod getStringValue = metaDummy.getMethod("getStringValue").orElseGet(Assertions::fail);
assertEquals(List.of(), getStringValue.getParameters());
assertEquals(String.class, getStringValue.getReturnParameter().getType());
assertTrue(Modifier.isPublic(getStringValue.getModifiers()));
MetaMethod privateMethod = metaDummy.getMethod("privateMethod").orElseGet(Assertions::fail);
assertEquals(List.of(), privateMethod.getParameters());
@ -51,29 +49,60 @@ public class ReflectiveTest {
@Test
void testInvokeGetter() {
Dummy dummy = new Dummy("bar");
MetaMethod getName = Reflective.getMetaClass(dummy.getClass()).getMethod("getName").orElseGet(Assertions::fail);
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
MetaMethod getStringValue = Reflective.getMetaClass(dummy.getClass()).getMethod("getStringValue").orElseGet(Assertions::fail);
// passing "foo" as the instance is not allowed
assertThrows(Panic.class, () -> getName.invoke("foo").unwrap());
assertThrows(Panic.class, () -> getStringValue.invoke("foo").unwrap());
// we should pass a valid dummy instance
assertEquals("bar", getName.invoke(dummy).unwrap());
assertEquals("don't panic!", getStringValue.invoke(dummy).unwrap());
}
@Test
void testInvokeSetter() {
Dummy dummy = new Dummy("bar");
void testInvokeSetters() {
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
MetaClass metaForClass = Reflective.getMetaClass(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
MetaMethod setByte = metaForClass.getMethod("setByteValue").orElseGet(Assertions::fail);
setByte.invoke(dummy, (byte) -42).unwrap();
assertEquals((byte) -42, dummy.getByteValue());
MetaMethod setShort = metaForClass.getMethod("setShortValue").orElseGet(Assertions::fail);
setShort.invoke(dummy, (short) -43).unwrap();
assertEquals((short) -43, dummy.getShortValue());
MetaMethod setInt = metaForClass.getMethod("setIntValue").orElseGet(Assertions::fail);
setInt.invoke(dummy, -44).unwrap();
assertEquals(-44, dummy.getIntValue());
MetaMethod setLongValue = metaForClass.getMethod("setLongValue").orElseGet(Assertions::fail);
setLongValue.invoke(dummy, -45L).unwrap();
assertEquals(-45L, dummy.getLongValue());
MetaMethod setFloat = metaForClass.getMethod("setFloatValue").orElseGet(Assertions::fail);
setFloat.invoke(dummy, -46.0F).unwrap();
assertEquals(-46.0F, dummy.getFloatValue());
MetaMethod setDouble = metaForClass.getMethod("setDoubleValue").orElseGet(Assertions::fail);
setDouble.invoke(dummy, -47.0).unwrap();
assertEquals(-47.0, dummy.getDoubleValue());
MetaMethod setChar = metaForClass.getMethod("setCharValue").orElseGet(Assertions::fail);
setChar.invoke(dummy, '-').unwrap();
assertEquals('-', dummy.getCharValue());
MetaMethod setBoolean = metaForClass.getMethod("setBooleanValue").orElseGet(Assertions::fail);
setBoolean.invoke(dummy, false).unwrap();
assertFalse(dummy.isBooleanValue());
MetaMethod setString = metaForClass.getMethod("setStringValue").orElseGet(Assertions::fail);
setString.invoke(dummy, "panic!").unwrap();
assertEquals("panic!", dummy.getStringValue());
}
@Test
void testInvocationExceptionHappened() {
Dummy dummy = new Dummy("bar");
Dummy dummy = new Dummy((byte) 42, (short) 43, 44, 45, 46.0F, 47.0, 'D', true, "don't panic!");
MetaClass metaForClass = Reflective.getMetaClass(dummy.getClass());
MetaMethod throwEx = metaForClass.getMethod("throwEx").orElseGet(Assertions::fail);
@ -81,25 +110,19 @@ public class ReflectiveTest {
assertFalse(result.isOk());
}
@Getter
@Setter
@AllArgsConstructor
public static class Dummy {
private String name;
public Dummy(String name) {
this.name = name;
}
public String getName() {
return privateMethod()[0];
}
public void setName(String name) {
this.name = name;
}
public void throwEx() throws Exception {
throw new Exception("something must have gone wrong");
}
private byte byteValue;
private short shortValue;
private int intValue;
private long longValue;
private float floatValue;
private double doubleValue;
private char charValue;
private boolean booleanValue;
private String stringValue;
@Override
public boolean equals(Object obj) {
@ -111,8 +134,15 @@ public class ReflectiveTest {
return 6;
}
private String[] privateMethod() {
return new String[]{name, "bar"};
@SuppressWarnings("unused")
private String[] privateMethod(){
return new String[1];
}
@SuppressWarnings("unused")
public void throwEx() {
throw new RuntimeException("ex");
}
}
}