aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/utils/InsnSequence.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ftbsc/lll/utils/InsnSequence.java')
-rw-r--r--src/main/java/ftbsc/lll/utils/InsnSequence.java138
1 files changed, 138 insertions, 0 deletions
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());
+ }
+}