diff --git a/src/main/java/ftbsc/lll/proxies/AbstractProxy.java b/src/main/java/ftbsc/lll/proxies/AbstractProxy.java new file mode 100644 index 0000000..8baf112 --- /dev/null +++ b/src/main/java/ftbsc/lll/proxies/AbstractProxy.java @@ -0,0 +1,119 @@ +package ftbsc.lll.proxies; + +/** + * Abstract proxy class, implementing common aspects + * of {@link MethodProxy} and {@link FieldProxy}. + * @since 0.3.0 + */ +public abstract class AbstractProxy { + + /** + * The SRG name of the corresponding class member. + */ + private final String srgName; + + /** + * The fully qualified name (i.e. java.lang.String) of + * the parent class. + */ + private final String parent; + + /** + * The modifiers of the member, as a packed int. + * @see java.lang.reflect.Modifier + */ + private final int modifiers; + + /** + * @return the SRG name of the item + */ + public String getSrgName() { + return this.srgName; + } + + /** + * @return the modifiers of the member, as a packed int + * @see java.lang.reflect.Modifier + */ + public int getModifiers() { + return this.modifiers; + } + + /** + * @return the fully qualified name of the parent class + */ + public String getParent() { + return this.parent; + } + + /** + * @return the descriptor of the member + */ + public abstract String getDescriptor(); + + /** + * The private constructor, should be called by all classes extending this in theirs. + * @param srgName the SRG name of the member + * @param modifiers the modifiers, as a packed int + * @param parent the FQN of the parent class + */ + protected AbstractProxy(String srgName, int modifiers, String parent) { + this.srgName = srgName; + this.modifiers = modifiers; + this.parent = parent; + } + + /** + * A Builder for the generic proxy. + * @param the type of proxy + */ + public abstract static class Builder { + + /** + * The SRG name of the member. + */ + protected final String srgName; + + /** + * The modifiers of the member, as a packed int. + */ + protected int modifiers; + + /** + * The fully qualified name of the parent. + */ + protected String parent; + + /** + * The constructor. + * @param srgName the SRG name of the member + */ + protected Builder(String srgName) { + this.srgName = srgName; + this.modifiers = 0; + } + + /** + * @param parentFQN the fully qualified name of the parent + * @return the current state of the builder + */ + public Builder setParent(String parentFQN) { + this.parent = parentFQN; + return this; + } + + /** + * @param newModifier the modifier to add + * @return the current state of the builder + */ + public Builder addModifier(int newModifier) { + this.modifiers |= newModifier; + return this; + } + + /** + * @return the built proxy object + */ + public abstract T build(); + } +} diff --git a/src/main/java/ftbsc/lll/proxies/FieldProxy.java b/src/main/java/ftbsc/lll/proxies/FieldProxy.java new file mode 100644 index 0000000..2bd0115 --- /dev/null +++ b/src/main/java/ftbsc/lll/proxies/FieldProxy.java @@ -0,0 +1,113 @@ +package ftbsc.lll.proxies; + +import ftbsc.lll.tools.DescriptorBuilder; +import org.objectweb.asm.Type; + +import java.lang.reflect.Field; + +/** + * A container for information about class fields to be used + * in ASM patching. + * @since 0.3.0 + */ +public class FieldProxy extends AbstractProxy { + + /** + * The descriptor of the field's type. + */ + private final String typeDescriptor; + + /** + * A public constructor, builds a proxy from a {@link Field} + * obtained from reflection. + * @param f the {@link Field} object corresponding to this. + */ + public FieldProxy(Field f) { + super(f.getName(), f.getModifiers(), Type.getInternalName(f.getDeclaringClass())); + this.typeDescriptor = Type.getDescriptor(f.getType()); + } + + /** + * A protected constructor, called only from the builder. + * @param srgName the SRG name of the field + * @param modifiers the modifiers of the field + * @param parent the FQN of the parent class of the field + * @param typeDescriptor the type descriptor of the field + */ + FieldProxy(String srgName, int modifiers, String parent, String typeDescriptor) { + super(srgName, modifiers, parent); + this.typeDescriptor = typeDescriptor; + } + + /** + * @return the field's type descriptor + */ + @Override + public String getDescriptor() { + return typeDescriptor; + } + + /** + * Returns a new instance of {@link FieldProxy.Builder}. + * @param srgName the SRG name of the field + * @return the builder object for field proxies + */ + public static Builder builder(String srgName) { + return new Builder(srgName); + } + + public static class Builder extends AbstractProxy.Builder { + /** + * The descriptor of the field's type. + */ + private String typeDescriptor; + + /** + * The constructor of the builder, used only internally. + * @param srgName the SRG name of the field + */ + Builder(String srgName) { + super(srgName); + } + + /** + * Sets the descriptor of the field type to the given {@link String}. + * @param typeDescriptor the descriptor of the field type + * @return the builder's state after the change + */ + public Builder setDescriptor(String typeDescriptor) { + this.typeDescriptor = typeDescriptor; + return this; + } + + /** + * Sets the descriptor of the field type to match the give {@link Class}. + * @param fqn the fully qualified name of the field type + * @param arrayLevel the array level of the field type + * @return the builder's state after the change + */ + public Builder setType(String fqn, int arrayLevel) { + this.typeDescriptor = DescriptorBuilder.nameToDescriptor(fqn, arrayLevel); + return this; + } + + /** + * Sets the descriptor of the field type to match the give {@link Class}. + * @param type a {@link Class} object representing the field type + * @return the builder's state after the change + */ + public Builder setType(Class type) { + this.typeDescriptor = Type.getDescriptor(type); + return this; + } + + /** + * Builds a {@link FieldProxy} of the given kind. + * @return the built {@link FieldProxy} + */ + @Override + public FieldProxy build() { + return new FieldProxy(this.srgName, this.modifiers, this.parent, this.typeDescriptor); + } + } +} diff --git a/src/main/java/ftbsc/lll/proxies/MethodProxy.java b/src/main/java/ftbsc/lll/proxies/MethodProxy.java new file mode 100644 index 0000000..1df4678 --- /dev/null +++ b/src/main/java/ftbsc/lll/proxies/MethodProxy.java @@ -0,0 +1,223 @@ +package ftbsc.lll.proxies; + +import ftbsc.lll.tools.DescriptorBuilder; +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * A container for information about class methods to be used + * in ASM patching. + * @since 0.3.0 + */ +public class MethodProxy extends AbstractProxy { + + /** + * The parameters of the method. + * It holds fully qualified names for objects, and {@link Class} + * objects for primitives. + */ + private final Object[] parameters; + + /** + * The return type of the method. + * It contains if it's an object, or a {@link Class} + * object for primitives. + */ + private final Object returnType; + + /** + * Caches the the descriptor after generating it once for + * performance. + */ + private String descriptorCache; + + /** + * A public constructor, builds a proxy from a {@link Method} + * obtained from reflection. + * @param m the {@link Method} object corresponding to this. + */ + public MethodProxy(Method m) { + super(m.getName(), m.getModifiers(), Type.getInternalName(m.getDeclaringClass())); + List parameters = new ArrayList<>(); + for(Class p : m.getParameterTypes()) + parameters.add(p.isPrimitive() ? p : new TypeContainer(p)); + this.parameters = parameters.toArray(); + Class returnType = m.getReturnType(); + this.returnType = returnType.isPrimitive() ? returnType : new TypeContainer(returnType); + } + + /** + * A protected constructor, called only from the builder. + * @param srgName the SRG name of the method + * @param modifiers the modifiers of the method + * @param parent the FQN of the parent class of the method + * @param parameters the parameters of the method + * @param returnType the return type of the method + */ + protected MethodProxy(String srgName, int modifiers, String parent, Object[] parameters, Object returnType) { + super(srgName, modifiers, parent); + this.parameters = parameters; + this.returnType = returnType; + this.descriptorCache = null; + } + + /** + * Builds (or returns from cache if present) + * the method's descriptor. + * @return the method's descriptor + */ + @Override + public String getDescriptor() { + if(this.descriptorCache != null) + return this.descriptorCache; + DescriptorBuilder b = new DescriptorBuilder(); + for(Object p : this.parameters) + addParameterToBuilder(b, p); + addParameterToBuilder(b, this.returnType); + this.descriptorCache = b.build(); + return this.descriptorCache; + } + + /** + * A static method used internally to correctly insert a + * {@link TypeContainer} into a {@link DescriptorBuilder}. + * @param b the {@link DescriptorBuilder} + * @param p the {@link TypeContainer} + */ + private static void addParameterToBuilder(DescriptorBuilder b, Object p) { + if(p instanceof TypeContainer) { + TypeContainer param = (TypeContainer) p; + b.addParameter(param.fqn, param.arrayLevel); + } else b.addParameter((Class) p); + } + + /** + * Returns a new instance of {@link MethodProxy.Builder}. + * @param srgName the SRG name of the method + * @return the builder object for method proxies + */ + public static Builder builder(String srgName) { + return new Builder(srgName); + } + + /** + * A builder object for {@link MethodProxy}. + */ + public static class Builder extends AbstractProxy.Builder { + /** + * The parameters of the method. + */ + private final List parameters; + + /** + * The return type of the method. Defaults to void. + */ + private Object returnType; + + /** + * The constructor of the builder, used only internally. + * @param srgName the SRG name of the method + */ + Builder(String srgName) { + super(srgName); + this.parameters = new ArrayList<>(); + this.returnType = void.class; + } + + /** + * Adds a parameter of a given type. + * @param fqn the fully qualified name of the parameter type + * @param arrayLevel the array level of the parameter type + * @return the builder's state after the change + */ + public Builder addParameter(String fqn, int arrayLevel) { + this.parameters.add(new TypeContainer(fqn, arrayLevel)); + return this; + } + + /** + * Adds a parameter of a given type. + * @param paramType the {@link Class} object corresponding to + * the parameter type. + * @return the builder's state after the change + */ + public Builder addParameter(Class paramType) { + this.parameters.add(paramType); + return this; + } + + /** + * Sets the return type to the given type. + * @param fqn the fully qualified name of the return type + * @param arrayLevel the array level of the return type + * @return the builder's state after the change + */ + public Builder setReturnType(String fqn, int arrayLevel) { + this.returnType = new TypeContainer(fqn, arrayLevel); + return this; + } + + /** + * Sets the return type to the given type. + * @param returnType the {@link Class} object corresponding to + * the return type + * @return the builder's state after the change + */ + public Builder setReturnType(Class returnType) { + this.returnType = returnType; + return this; + } + + /** + * Builds a {@link MethodProxy} of the given kind. + * @return the built {@link MethodProxy} + */ + @Override + public MethodProxy build() { + return new MethodProxy(srgName, modifiers, parent, parameters.toArray(), returnType); + } + } + + /** + * A container class, holding information about a given type. + */ + protected static class TypeContainer { + /** + * The fully qualified name of the type. + */ + public final String fqn; + + /** + * The array level of the type. + */ + public final int arrayLevel; + + /** + * Public constructor for the class. + * @param fqn the fully qualified name of the type + * @param arrayLevel the array level of the type + */ + public TypeContainer(String fqn, int arrayLevel) { + this.fqn = fqn; + this.arrayLevel = arrayLevel; + } + + /** + * Public constructor for the class, extracting the + * necessary information from a {@link Class} object. + * @param clazz the class object + */ + public TypeContainer(Class clazz) { + int arrayLevel = 0; + while(clazz.isArray()) { + arrayLevel++; + clazz = clazz.getComponentType(); + } + this.arrayLevel = arrayLevel; + this.fqn = clazz.getCanonicalName(); + } + } +} diff --git a/src/main/java/ftbsc/lll/tools/StackTools.java b/src/main/java/ftbsc/lll/tools/StackTools.java index 8cb4504..dbf1858 100644 --- a/src/main/java/ftbsc/lll/tools/StackTools.java +++ b/src/main/java/ftbsc/lll/tools/StackTools.java @@ -1,5 +1,6 @@ package ftbsc.lll.tools; +import ftbsc.lll.proxies.MethodProxy; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; @@ -62,7 +63,7 @@ public class StackTools implements Opcodes { } /** - * Creates a new local variable, lasting in scope between two given LabelNodes. + * Creates a new local variable, lasting in scope between two given {@link LabelNode}s. * @param method the method for which to declare the local variable * @param name the variable's name * @param desc the type descriptor for the new variable @@ -83,4 +84,19 @@ public class StackTools implements Opcodes { method.localVariables.add(variable); return targetIndex; } + + /** + * Calls the given {@link MethodProxy} with the given opcode. + * @param m a {@link MethodProxy} representing the method to call. + * @return a {@link MethodInsnNode} representing the build node. + * @since 0.3.0 + */ + public static MethodInsnNode call(MethodProxy m, int opcode) { + return new MethodInsnNode( + opcode, + m.getParent().replace('.', '/'), + m.getSrgName(), + m.getDescriptor() + ); + } }