summaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ftbsc/lll/utils')
-rw-r--r--src/main/java/ftbsc/lll/utils/DescriptorBuilder.java141
-rw-r--r--src/main/java/ftbsc/lll/utils/InsnSequence.java138
-rw-r--r--src/main/java/ftbsc/lll/utils/PatternMatcher.java250
-rw-r--r--src/main/java/ftbsc/lll/utils/StackUtils.java86
-rw-r--r--src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java79
-rw-r--r--src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java22
-rw-r--r--src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java23
-rw-r--r--src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java23
8 files changed, 762 insertions, 0 deletions
diff --git a/src/main/java/ftbsc/lll/utils/DescriptorBuilder.java b/src/main/java/ftbsc/lll/utils/DescriptorBuilder.java
new file mode 100644
index 0000000..541497b
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/DescriptorBuilder.java
@@ -0,0 +1,141 @@
+package ftbsc.lll.utils;
+
+import org.objectweb.asm.Type;
+
+import java.util.ArrayList;
+
+/**
+ * Builds a method descriptor for you.
+ * See the <a href="https://asm.ow2.io/asm4-guide.pdf">documentation</a> to better understand what this is.
+ * Parameters must be given in a specific order.
+ * Return type should always be specified for clarity, but defaults to void.
+ */
+public class DescriptorBuilder {
+ /**
+ * The descriptor of the return type.
+ */
+ private String returnType;
+
+ /**
+ * The descriptors of the parameters.
+ */
+ private final ArrayList<String> params;
+
+ /**
+ * Public constructor.
+ * Initialises default values.
+ */
+ public DescriptorBuilder() {
+ this.returnType = Type.getDescriptor(void.class);
+ this.params = new ArrayList<>();
+ }
+
+ /**
+ * Sets the return type to the given type.
+ * Passing a {@link Class} may cause problems if used with objects outside the Java
+ * SDK. Pass the fully qualified name as a {@link String} rather than the {@link Class}
+ * object for non-standard types.
+ * @param returnType the Class object corresponding to the return type
+ * @return the builder's state after the change
+ */
+ public DescriptorBuilder setReturnType(Class<?> returnType) {
+ this.returnType = Type.getDescriptor(returnType);
+ return this;
+ }
+
+ /**
+ * Sets the return type to the Object specified here as a fully
+ * qualified name. Example: java.lang.String.
+ * No validity checks are performed: it's up to the user to ensure the name is correct.
+ * @param returnType the fully qualified name of the desired Object.
+ * @return the builder's state after the change
+ */
+ public DescriptorBuilder setReturnType(String returnType) {
+ return this.setReturnType(returnType, 0);
+ }
+
+ /**
+ * Sets the return type to the Object specified here as a fully
+ * qualified name (example: java.lang.String), with the specified array level.
+ * No validity checks are performed: it's up to the user to ensure the name is correct.
+ * @param returnType the fully qualified name of the desired Object.
+ * @param arrayLevel how many levels of array are there
+ * (example: String is 0, String[] is 1, String[][] is 2, etc.)
+ * @return the builder's state after the change
+ */
+ public DescriptorBuilder setReturnType(String returnType, int arrayLevel) {
+ this.returnType = nameToDescriptor(returnType, arrayLevel);
+ return this;
+ }
+
+ /**
+ * Adds a parameter of the given class type to the method.
+ * Parameter order matters.
+ * Passing a {@link Class} may cause problems if used with objects outside the Java
+ * SDK. Pass the fully qualified name as a {@link String} rather than the {@link Class}
+ * object for non-standard types.
+ * @param param the Class object corresponding to the parameter
+ * @return the builder's state after the change
+ */
+ public DescriptorBuilder addParameter(Class<?> param) {
+ this.params.add(Type.getDescriptor(param));
+ return this;
+ }
+
+ /**
+ * Adds a parameter with the type specified by the given fully
+ * qualified name to the method. Example: java.lang.String.
+ * Parameter order matters.
+ * No validity checks are performed: it's up to the user to ensure the name is correct.
+ * @param param the fully qualified name of the parameter type
+ * @return the builder's state after the change
+ */
+ public DescriptorBuilder addParameter(String param) {
+ return this.addParameter(param, 0);
+ }
+
+ /**
+ * Adds a parameter with the type specified by the given fully
+ * qualified name (example: java.lang.String) to the method, with
+ * the specified array level.
+ * Parameter order matters.
+ * No validity checks are performed: it's up to the user to ensure the name is correct.
+ * @param param the fully qualified name of the parameter type
+ * @param arrayLevel how many levels of array are there
+ * (example: String is 0, String[] is 1, String[][] is 2, etc.)
+ * @return the builder's state after the change
+ */
+ public DescriptorBuilder addParameter(String param, int arrayLevel) {
+ this.params.add(nameToDescriptor(param, arrayLevel));
+ return this;
+ }
+
+ /**
+ * Builds the descriptor into a string.
+ * Example result: {@code int m(Object[] o)} becomes {@code ([Ljava/lang/Object;)I}
+ * @return the resulting descriptor
+ */
+ public String build() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('(');
+ for(String p : params)
+ sb.append(p);
+ sb.append(')').append(returnType);
+ return sb.toString();
+ }
+
+ /**
+ * Converts a fully qualified name and array level to a descriptor.
+ * @param name the fully qualified name of the object type
+ * @param arrayLevel how many levels of array are there
+ * (example: String is 0, String[] is 1, String[][] is 2, etc.)
+ * @return object descriptor
+ */
+ public static String nameToDescriptor(String name, int arrayLevel) {
+ StringBuilder sb = new StringBuilder();
+ for(int i = 0; i < arrayLevel; i++)
+ sb.append('[');
+ sb.append('L').append(name.replace('.', '/')).append(';');
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/InsnSequence.java b/src/main/java/ftbsc/lll/utils/InsnSequence.java
new file mode 100644
index 0000000..4734505
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/InsnSequence.java
@@ -0,0 +1,138 @@
+package ftbsc.lll.utils;
+
+import ftbsc.lll.exceptions.InstructionMismatchException;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+
+import java.util.Objects;
+
+/**
+ * Represents a sequence of instructions contained within two given nodes.
+ * Extends {@link InsnList}, but provides additional flexibility and features.
+ */
+public class InsnSequence extends InsnList {
+ /**
+ * Public constructor.
+ * This creates an empty sequence.
+ */
+ public InsnSequence() {
+ super();
+ }
+
+ /**
+ * Public constructor for list with single item.
+ * Must be given a single non-null node.
+ * @param node the node in question
+ */
+ public InsnSequence(AbstractInsnNode node) {
+ super();
+ this.add(node);
+ }
+
+ /**
+ * Public constructor.
+ * Must be given two non-null, connected nodes.
+ * @param startNode the starting node of the pattern
+ * @param endNode the first node of the pattern
+ */
+ public InsnSequence(AbstractInsnNode startNode, AbstractInsnNode endNode) {
+ Objects.requireNonNull(startNode);
+ Objects.requireNonNull(endNode);
+ for(; startNode != null; startNode = startNode.getNext()) {
+ this.add(startNode);
+ if(startNode == endNode) break;
+ }
+ if(startNode == null)
+ throw new InstructionMismatchException("Nodes" + getFirst() + " and " + getLast() + " are not connected.");
+ }
+
+ /**
+ * Extends the existing get function from InsnList to allow for negative indexes.
+ * @param index the index of the instruction that must be returned
+ * @return the instruction whose index is given
+ */
+ @Override
+ public AbstractInsnNode get(int index) {
+ if(index >= 0)
+ return super.get(index);
+ index = Math.abs(index);
+ if(index > size())
+ throw new IndexOutOfBoundsException();
+ return super.get(size() - index);
+ }
+
+ /**
+ * Adds an array of nodes to the list.
+ * @param nodes the nodes to add
+ */
+ public void add(AbstractInsnNode... nodes) {
+ for(AbstractInsnNode node : nodes)
+ this.add(node);
+ }
+
+ /**
+ * Wraps InsnList's add() to throw an exception
+ * when given null values.
+ * @param node to add
+ */
+ @Override
+ public void add(AbstractInsnNode node) {
+ Objects.requireNonNull(node);
+ super.add(node);
+ }
+
+ /**
+ * Replaces a node with another one. Mostly used internally.
+ * @param oldNode node to replace
+ * @param newNode new node
+ */
+ public void replaceNode(AbstractInsnNode oldNode, AbstractInsnNode newNode) {
+ super.insert(oldNode, newNode);
+ super.remove(oldNode);
+ }
+
+ /**
+ * Replaces n occurrences of said opcode with the given node.
+ * @param opcode the opcode to replace
+ * @param newNode the replacement node
+ * @param amount how many occurrences to replace, set to 0 to replace all
+ * @return true if anything was changed, false otherwise
+ */
+ public boolean replace(int opcode, AbstractInsnNode newNode, int amount) {
+ return replace(opcode, newNode, amount, false);
+ }
+
+ /**
+ * Replaces n occurrences of said opcode with the given node.
+ * @param opcode the opcode to replace
+ * @param newNode the replacement node
+ * @param reverse whether the search should be done from the end
+ * @param amount how many occurrences to replace, set to 0 to replace all
+ * @return true if anything was changed, false otherwise
+ */
+ public boolean replace(int opcode, AbstractInsnNode newNode, int amount, boolean reverse) {
+ boolean changed = false;
+ for(AbstractInsnNode cur = this.getFirst();
+ cur != null && cur.getPrevious() != this.getLast() && cur.getNext() != this.getFirst();
+ cur = reverse ? cur.getPrevious() : cur.getNext()) {
+ if(cur.getOpcode() == opcode) {
+ this.replaceNode(cur, newNode);
+ changed = true;
+ amount--; // will go to negative if it was already 0, causing it to go on until for loop finishes
+ if(amount == 0)
+ return changed;
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Cut a number of nodes from the list.
+ * @param amount how many nodes to cut
+ * @param reverse true if should cut from the end, false otherwise
+ */
+ public void cut(int amount, boolean reverse) {
+ for(int i = 0; i < amount; i++)
+ this.remove(reverse ? getLast() : getFirst());
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/PatternMatcher.java b/src/main/java/ftbsc/lll/utils/PatternMatcher.java
new file mode 100644
index 0000000..9518749
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/PatternMatcher.java
@@ -0,0 +1,250 @@
+package ftbsc.lll.utils;
+
+import ftbsc.lll.exceptions.PatternNotFoundException;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Describes a pattern to match on a list of ASM instructions.
+ */
+public class PatternMatcher {
+ /**
+ * The list of predicates to match.
+ */
+ private final List<Predicate<AbstractInsnNode>> predicates;
+
+ /**
+ * Whether pattern search should be done from the end.
+ */
+ private final boolean reverse;
+
+ /**
+ * Patterns flagged with this ignore labels.
+ */
+ private final boolean ignoreLabels;
+
+ /**
+ * Patterns flagged with this ignore FRAME instructions.
+ */
+ private final boolean ignoreFrames;
+
+ /**
+ * Patterns flagged with this ignore LINENUMBER instructions.
+ */
+ private final boolean ignoreLineNumbers;
+
+ /**
+ * Private constructor because a PatternMatcher should only ever be initialized
+ * through the builder.
+ * @param predicates the list of predicates to match
+ * @param reverse search direction
+ * @param ignoreLabels whether LABEL instructions should be ignored
+ * @param ignoreFrames whether FRAME instructions should be ignored
+ * @param ignoreLineNumbers whether LINENUMBER instructions should be ignored
+ */
+ private PatternMatcher(List<Predicate<AbstractInsnNode>> predicates, boolean reverse,
+ boolean ignoreLabels, boolean ignoreFrames, boolean ignoreLineNumbers) {
+ this.predicates = predicates;
+ this.reverse = reverse;
+ this.ignoreLabels = ignoreLabels;
+ this.ignoreFrames = ignoreFrames;
+ this.ignoreLineNumbers = ignoreLineNumbers;
+ }
+
+ /**
+ * @return the Builder object for this {@link PatternMatcher}
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Tries to match the given pattern on a given {@link MethodNode}.
+ * @param node the {@link MethodNode} to search
+ * @return the InsnSequence object representing the matched pattern
+ */
+ public InsnSequence find(MethodNode node) {
+ return find(reverse ? node.instructions.getLast() : node.instructions.getFirst());
+ }
+
+ /**
+ * Tries to match the given pattern starting from a given node.
+ * @param node the node to start the search on
+ * @return the {@link InsnSequence} object representing the matched pattern
+ */
+ public InsnSequence find(AbstractInsnNode node) {
+ if(node != null) {
+ AbstractInsnNode first, last;
+ for(AbstractInsnNode cur = node; cur != null; cur = reverse ? cur.getPrevious() : cur.getNext()) {
+ if(predicates.size() == 0) return new InsnSequence(cur); //match whatever
+ first = cur;
+ last = cur;
+ for(int match = 0; last != null && match < predicates.size(); last = reverse ? last.getPrevious() : last.getNext()) {
+ if(match != 0) {
+ if(ignoreLabels && last.getType() == AbstractInsnNode.LABEL) continue;
+ if(ignoreFrames && last.getType() == AbstractInsnNode.FRAME) continue;
+ if(ignoreLineNumbers && last.getType() == AbstractInsnNode.LINE) continue;
+ }
+ if(!predicates.get(match).test(last)) break;
+ if(match == predicates.size() - 1) {
+ if(reverse) return new InsnSequence(last, first); //we are matching backwards
+ else return new InsnSequence(first, last);
+ } else match++;
+ }
+ }
+ }
+ throw new PatternNotFoundException("Failed to find pattern!");
+ }
+
+ /**
+ * The Builder object for {@link PatternMatcher}.
+ */
+ public static class Builder {
+
+ /**
+ * List of predicates the pattern has to match.
+ */
+ private final List<Predicate<AbstractInsnNode>> predicates = new ArrayList<>();
+
+ /**
+ * Whether the pattern matching should proceed in reversed order.
+ */
+ private boolean reverse = false;
+
+ /**
+ * Patterns flagged with this ignore labels.
+ */
+ private boolean ignoreLabels = false;
+
+ /**
+ * Patterns flagged with this ignore FRAME instructions.
+ */
+ private boolean ignoreFrames = false;
+
+ /**
+ * Patterns flagged with this ignore LINENUMBER instructions.
+ */
+ private boolean ignoreLineNumbers = false;
+
+ /**
+ * Builds the pattern defined so far.
+ * @return the built {@link PatternMatcher}
+ */
+ public PatternMatcher build() {
+ return new PatternMatcher(predicates, reverse, ignoreLabels, ignoreFrames, ignoreLineNumbers);
+ }
+
+ /**
+ * Sets the pattern to match starting from the end.
+ * @return the builder's state after the operation
+ */
+ public Builder reverse() {
+ this.reverse = true;
+ return this;
+ }
+
+ /**
+ * Adds a custom predicate to the list. Also used internally.
+ * @param predicate the predicate to add
+ * @return the builder's state after the operation
+ */
+ public Builder check(Predicate<AbstractInsnNode> predicate) {
+ predicates.add(predicate);
+ return this;
+ }
+
+ /**
+ * Wildcard, matches any kind of node.
+ * @return the builder's state after the operation
+ */
+ public Builder any() {
+ return check(i -> true);
+ }
+
+ /**
+ * Matches a specific opcode.
+ * @param opcode opcode to match
+ * @return the builder's state after the operation
+ */
+ public Builder opcode(int opcode) {
+ return check(i -> i.getOpcode() == opcode);
+ }
+
+ /**
+ * Matches a list of opcodes.
+ * @param opcodes list of opcodes to match
+ * @return the builder's state after the operation
+ */
+ public Builder opcodes(int... opcodes) {
+ Builder res = this;
+ for(int o : opcodes)
+ res = opcode(o);
+ return res;
+ }
+
+ /**
+ * Matches a method invokation of any kind: one of INVOKEVIRTUAL,
+ * INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
+ * @return the builder's state after the operation
+ */
+ public Builder method() {
+ return check(i -> i.getType() == AbstractInsnNode.METHOD_INSN);
+ }
+
+ /**
+ * Matches a field invokation of any kind: one of GETSTATIC, PUTSTATIC,
+ * GETFIELD or PUTFIELD.
+ * @return the builder's state after the operation
+ */
+ public Builder field() {
+ return check(i -> i.getType() == AbstractInsnNode.FIELD_INSN);
+ }
+
+ /**
+ * Matches any kind of jump instruction.
+ * @return the builder's state after the operation
+ */
+ public Builder jump() {
+ return check(i -> i.getType() == AbstractInsnNode.JUMP_INSN);
+ }
+
+ /**
+ * Matches any kind of label.
+ * @return the builder's state after the operation
+ */
+ public Builder label() {
+ return check(i -> i.getType() == AbstractInsnNode.LABEL);
+ }
+
+ /**
+ * Tells the pattern matcher to ignore LABEL instructions.
+ * @return the builder's state after the operation
+ */
+ public Builder ignoreLabels() {
+ this.ignoreLabels = true;
+ return this;
+ }
+
+ /**
+ * Tells the pattern matcher to ignore FRAME instructions.
+ * @return the builder's state after the operation
+ */
+ public Builder ignoreFrames() {
+ this.ignoreFrames = true;
+ return this;
+ }
+
+ /**
+ * Tells the pattern matcher to ignore LINENUMBER instructions.
+ * @return the builder's state after the operation
+ */
+ public Builder ignoreLineNumbers() {
+ this.ignoreLineNumbers = true;
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/StackUtils.java b/src/main/java/ftbsc/lll/utils/StackUtils.java
new file mode 100644
index 0000000..225b1ee
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/StackUtils.java
@@ -0,0 +1,86 @@
+package ftbsc.lll.utils;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.*;
+
+import java.util.Comparator;
+
+/**
+ * Various methods for manipulating the stack.
+ * Includes anything from instantiation to variable manipulation - just about
+ * anything that loads stuff on or from the stack.
+ */
+public class StackUtils implements Opcodes {
+ /**
+ * Creates a new instance of an object, given its internal name, constructor descriptor and instructions to load
+ * the parameters.
+ * The created object is loaded on the stack.
+ * @param name the internal name of the object to initialize, where
+ * the internal name of a class is its fully qualified name, where '.' are replaced by '/'
+ * @param desc the descriptor of the constructor to call
+ * @param args nodes containing instructions to load the constructor arguments, in the right order
+ * @return an instruction list containing the opcodes needed to create the new object and load it on the stack.
+ */
+ public static InsnList instantiate(String name, String desc, AbstractInsnNode... args) {
+ InsnSequence is = new InsnSequence();
+ is.add(args);
+ return instantiate(name, desc, is);
+ }
+
+ /**
+ * Creates a new instance of an object, given its internal name, constructor descriptor and instructions to load
+ * the parameters.
+ * The created object is loaded on the stack.
+ * @param name the internal name of the object to initialize, where
+ * the internal name of a class is its fully qualified name, where '.' are replaced by '/'
+ * @param desc the descriptor of the constructor to call
+ * @param args a list of instructions loading the constructor arguments onto the stack in the correct order
+ * @return an instruction list containing the opcodes needed to create the new object and load it on the stack.
+ */
+ public static InsnList instantiate(String name, String desc, InsnList args) {
+ InsnSequence list = new InsnSequence();
+ list.add(new TypeInsnNode(NEW, name), new InsnNode(DUP));
+ if(args != null) list.add(args);
+ list.add(new MethodInsnNode(INVOKESPECIAL, name, "<init>", desc, false));
+ return list;
+ }
+
+
+ /**
+ * Creates a new local variable, lasting in scope from the first to the last label of the given method.
+ * @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
+ * @return the index value of the new local variable
+ */
+ public static int addLocalVariable(MethodNode method, String name, String desc) {
+ return addLocalVariable(
+ method, name, desc,
+ (LabelNode) PatternMatcher.builder().label().build().find(method).getFirst(),
+ (LabelNode) PatternMatcher.builder().label().reverse().build().find(method).getFirst()
+ );
+ }
+
+ /**
+ * 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
+ * @param start the label at which the variable should enter scope
+ * @param end the label at which the variable should go out of scope
+ * @return the index value of the new local variable
+ */
+ public static int addLocalVariable(MethodNode method, String name, String desc, LabelNode start, LabelNode end) {
+ final int targetIndex =
+ method.localVariables
+ .stream()
+ .max(Comparator.comparingInt(v -> v.index))
+ .map(var -> var.desc.equals("J") || var.desc.equals("D")
+ ? var.index + 2 //skip two if long or double - major java moment
+ : var.index + 1)
+ .orElse(0);
+ LocalVariableNode variable = new LocalVariableNode(name, desc, null, start, end, targetIndex);
+ method.localVariables.add(variable);
+ return targetIndex;
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java b/src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java
new file mode 100644
index 0000000..c58fba4
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java
@@ -0,0 +1,79 @@
+package ftbsc.lll.utils.debug;
+
+import org.apache.logging.log4j.Logger;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.util.Printer;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * A collection of static methods for debugging by printing the ASM bytecode.
+ * These methods are only for debug, so most of the time they should stay unused.
+ */
+public class BytecodePrinter {
+
+ /**
+ * Used for converting visit events to text, acts pretty much like a buffer in our case.
+ */
+ private static final Printer PRINTER = new Textifier();
+
+ /**
+ * MethodVisitor that visits the method and prints it to a given printer.
+ */
+ private static final TraceMethodVisitor MP = new TraceMethodVisitor(PRINTER);
+
+ /**
+ * Prints the bytecode of a method using System.out.print().
+ * @param main the method to print
+ */
+ public static void printMethod(final MethodNode main) {
+ for (AbstractInsnNode i : main.instructions.toArray())
+ System.out.print(insnToString(i));
+ }
+
+ /**
+ * Logs the bytecode of a method using the ASM logger.
+ * @param main the method to print
+ * @param logger the Log4j {@link Logger} to print it with
+ */
+ public static void logMethod(final MethodNode main, final Logger logger) {
+ for (AbstractInsnNode i : main.instructions.toArray())
+ logger.debug(insnToString(i));
+ }
+
+ /**
+ * Logs the bytecode of a method to a file.
+ * @param main the method to print
+ * @param path the file to log it to
+ */
+ public static void logMethod(final MethodNode main, String path) {
+ StringBuilder out = new StringBuilder();
+ for (AbstractInsnNode i : main.instructions.toArray())
+ out.append(insnToString(i));
+ try {
+ Files.write(Paths.get(path), out.toString().getBytes());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Converts an instruction node to a String.
+ * @param insn the node to convert
+ * @return the converted string
+ */
+ public static String insnToString(AbstractInsnNode insn) {
+ insn.accept(MP);
+ StringWriter sw = new StringWriter();
+ PRINTER.print(new PrintWriter(sw));
+ PRINTER.getText().clear();
+ return sw.toString();
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java b/src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java
new file mode 100644
index 0000000..e809ccd
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java
@@ -0,0 +1,22 @@
+package ftbsc.lll.utils.nodes;
+
+import ftbsc.lll.proxies.impl.FieldProxy;
+import org.objectweb.asm.tree.FieldInsnNode;
+
+/**
+ * Overrides the {@link FieldInsnNode} to add a constructor
+ * taking in a {@link FieldProxy}.
+ * @since 0.3.0
+ */
+public class FieldProxyInsnNode extends FieldInsnNode {
+ /**
+ * Constructs a new {@link FieldInsnNode} starting
+ * from a {@link FieldProxy}.
+ * @param opcode the opcode, must be one of GETSTATIC, PUTSTATIC,
+ * GETFIELD or PUTFIELD
+ * @param f a {@link FieldProxy} representing the field to call
+ */
+ public FieldProxyInsnNode(int opcode, FieldProxy f) {
+ super(opcode, f.parent.internalName, f.name, f.descriptor);
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java b/src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java
new file mode 100644
index 0000000..2e062f4
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java
@@ -0,0 +1,23 @@
+package ftbsc.lll.utils.nodes;
+
+import ftbsc.lll.proxies.impl.MethodProxy;
+import org.objectweb.asm.tree.MethodInsnNode;
+
+/**
+ * Overrides the {@link MethodInsnNode} to add a constructor
+ * taking in a {@link MethodProxy}.
+ * @since 0.3.0
+ */
+public class MethodProxyInsnNode extends MethodInsnNode {
+
+ /**
+ * Constructs a new {@link MethodInsnNode} starting
+ * from a {@link MethodProxy}.
+ * @param opcode the opcode, must be one of INVOKEVIRTUAL,
+ * INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE
+ * @param m a {@link MethodProxy} representing the method to call
+ */
+ public MethodProxyInsnNode(int opcode, MethodProxy m) {
+ super(opcode, m.parent.internalName, m.name, m.descriptor);
+ }
+}
diff --git a/src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java b/src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java
new file mode 100644
index 0000000..3648278
--- /dev/null
+++ b/src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java
@@ -0,0 +1,23 @@
+package ftbsc.lll.utils.nodes;
+
+import ftbsc.lll.proxies.impl.TypeProxy;
+import org.objectweb.asm.tree.TypeInsnNode;
+
+/**
+ * Overrides the {@link TypeInsnNode} to add a constructor
+ * taking in a {@link TypeProxy}.
+ * @since 0.4.0
+ */
+public class TypeProxyInsnNode extends TypeInsnNode {
+ /**
+ * Constructs a new {@link TypeInsnNode} starting from a
+ * {@link TypeProxy}. The user should ensure that the TypeInsnNode
+ * represents a declared type before calling this.
+ * @param opcode the opcode, must be one of NEW, ANEWARRAY,
+ * CHECKCAST or INSTANCEOF
+ * @param t a {@link TypeProxy} representing the type to call
+ */
+ public TypeProxyInsnNode(int opcode, TypeProxy t) {
+ super(opcode, t.internalName);
+ }
+}