aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/tools/InsnSequence.java
blob: ae9bef006c20fdde4e789517a128925a2304598a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package ftbsc.lll.tools;

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 this.toArray()[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 fed 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());
   }
}