Skip to content

Commit 1c527c8

Browse files
committed
feat: match by node
1 parent 82ec51b commit 1c527c8

File tree

2 files changed

+217
-28
lines changed

2 files changed

+217
-28
lines changed

src/main/java/ftbsc/lll/IInjector.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ public interface IInjector {
5454

5555
/**
5656
* This method is to be called by the launcher after identifying the right class and
57-
* method to patch. The overriding method should contain the logic for actually
58-
* pathing.
57+
* method to patch. The overriding method should contain the actual patching logic.
5958
* @param clazz the {@link ClassNode} currently being patched
6059
* @param method the {@link MethodNode} of method currently being patched
6160
*/

src/main/java/ftbsc/lll/utils/PatternMatcher.java

+216-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package ftbsc.lll.utils;
22

33
import ftbsc.lll.exceptions.PatternNotFoundException;
4-
import org.objectweb.asm.tree.AbstractInsnNode;
5-
import org.objectweb.asm.tree.MethodNode;
4+
import ftbsc.lll.proxies.impl.FieldProxy;
5+
import ftbsc.lll.proxies.impl.MethodProxy;
6+
import ftbsc.lll.proxies.impl.TypeProxy;
7+
import org.objectweb.asm.tree.*;
68

79
import java.util.ArrayList;
810
import java.util.List;
11+
import java.util.Objects;
12+
import java.util.function.BiPredicate;
913
import java.util.function.Predicate;
1014

1115
/**
@@ -68,7 +72,7 @@ public static Builder builder() {
6872
* @return the InsnSequence object representing the matched pattern
6973
*/
7074
public InsnSequence find(MethodNode node) {
71-
return find(reverse ? node.instructions.getLast() : node.instructions.getFirst());
75+
return find(this.reverse ? node.instructions.getLast() : node.instructions.getFirst());
7276
}
7377

7478
/**
@@ -79,19 +83,19 @@ public InsnSequence find(MethodNode node) {
7983
public InsnSequence find(AbstractInsnNode node) {
8084
if(node != null) {
8185
AbstractInsnNode first, last;
82-
for(AbstractInsnNode cur = node; cur != null; cur = reverse ? cur.getPrevious() : cur.getNext()) {
83-
if(predicates.size() == 0) return new InsnSequence(cur); //match whatever
86+
for(AbstractInsnNode cur = node; cur != null; cur = this.reverse ? cur.getPrevious() : cur.getNext()) {
87+
if(this.predicates.isEmpty()) return new InsnSequence(cur); //match whatever
8488
first = cur;
8589
last = cur;
86-
for(int match = 0; last != null && match < predicates.size(); last = reverse ? last.getPrevious() : last.getNext()) {
90+
for(int match = 0; last != null && match < this.predicates.size(); last = this.reverse ? last.getPrevious() : last.getNext()) {
8791
if(match != 0) {
88-
if(ignoreLabels && last.getType() == AbstractInsnNode.LABEL) continue;
89-
if(ignoreFrames && last.getType() == AbstractInsnNode.FRAME) continue;
90-
if(ignoreLineNumbers && last.getType() == AbstractInsnNode.LINE) continue;
92+
if(this.ignoreLabels && last.getType() == AbstractInsnNode.LABEL) continue;
93+
if(this.ignoreFrames && last.getType() == AbstractInsnNode.FRAME) continue;
94+
if(this.ignoreLineNumbers && last.getType() == AbstractInsnNode.LINE) continue;
9195
}
92-
if(!predicates.get(match).test(last)) break;
93-
if(match == predicates.size() - 1) {
94-
if(reverse) return new InsnSequence(last, first); //we are matching backwards
96+
if(!this.predicates.get(match).test(last)) break;
97+
if(match == this.predicates.size() - 1) {
98+
if(this.reverse) return new InsnSequence(last, first); //we are matching backwards
9599
else return new InsnSequence(first, last);
96100
} else match++;
97101
}
@@ -153,7 +157,7 @@ public Builder reverse() {
153157
* @return the builder's state after the operation
154158
*/
155159
public Builder check(Predicate<AbstractInsnNode> predicate) {
156-
predicates.add(predicate);
160+
this.predicates.add(predicate);
157161
return this;
158162
}
159163

@@ -162,7 +166,7 @@ public Builder check(Predicate<AbstractInsnNode> predicate) {
162166
* @return the builder's state after the operation
163167
*/
164168
public Builder any() {
165-
return check(i -> true);
169+
return this.check(i -> true);
166170
}
167171

168172
/**
@@ -171,7 +175,7 @@ public Builder any() {
171175
* @return the builder's state after the operation
172176
*/
173177
public Builder opcode(int opcode) {
174-
return check(i -> i.getOpcode() == opcode);
178+
return this.check(i -> i.getOpcode() == opcode);
175179
}
176180

177181
/**
@@ -181,47 +185,223 @@ public Builder opcode(int opcode) {
181185
*/
182186
public Builder opcodes(int... opcodes) {
183187
Builder res = this;
184-
for(int o : opcodes)
185-
res = opcode(o);
188+
for(int o : opcodes) {
189+
res = this.opcode(o);
190+
}
191+
186192
return res;
187193
}
188194

189195
/**
190-
* Matches a method invokation of any kind: one of INVOKEVIRTUAL,
196+
* Matches a method invocation of any kind: one of INVOKEVIRTUAL,
191197
* INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
192198
* @return the builder's state after the operation
193199
*/
194200
public Builder method() {
195-
return check(i -> i.getType() == AbstractInsnNode.METHOD_INSN);
201+
return this.check(i -> i.getType() == AbstractInsnNode.METHOD_INSN);
196202
}
197203

198204
/**
199-
* Matches a field invokation of any kind: one of GETSTATIC, PUTSTATIC,
205+
* Matches a field invocation of any kind: one of GETSTATIC, PUTSTATIC,
200206
* GETFIELD or PUTFIELD.
201207
* @return the builder's state after the operation
202208
*/
203209
public Builder field() {
204-
return check(i -> i.getType() == AbstractInsnNode.FIELD_INSN);
210+
return this.check(i -> i.getType() == AbstractInsnNode.FIELD_INSN);
205211
}
206212

207213
/**
208214
* Matches any kind of jump instruction.
209215
* @return the builder's state after the operation
210216
*/
211217
public Builder jump() {
212-
return check(i -> i.getType() == AbstractInsnNode.JUMP_INSN);
218+
return this.check(i -> i.getType() == AbstractInsnNode.JUMP_INSN);
213219
}
214220

215221
/**
216222
* Matches any kind of label.
217223
* @return the builder's state after the operation
218224
*/
219225
public Builder label() {
220-
return check(i -> i.getType() == AbstractInsnNode.LABEL);
226+
return this.check(i -> i.getType() == AbstractInsnNode.LABEL);
221227
}
222228

223229
/**
224-
* Tells the pattern matcher to ignore LABEL instructions.
230+
* Matches the given opcode and the exact given arguments.
231+
* Partial argument matches are not supported: all arguments must be provided for
232+
* the check to succeed.
233+
* The expected order of arguments is the one used in the relevant node constructor;
234+
* where possible, a proxy can substitute the parent/name/descriptor arguments.
235+
* Lists may be used in place of arrays; varargs will also be supported where the
236+
* relevant node constructor accepted them. Raw labels may be used in place of LabelNodes.
237+
* Matches made using method are the safest, but other tests are generally faster,
238+
* although the difference will likely be negligible in nearly all use cases.
239+
* @param opcode the opcode
240+
* @param args the arguments (you may use proxies in place of name/descriptors)
241+
* @return the builder's state after the operation
242+
*/
243+
public Builder node(int opcode, Object... args) {
244+
return this.check(i -> matchNode(i, opcode, args));
245+
}
246+
247+
/**
248+
* Tests whether the arguments at the given index of the given array match the ones
249+
* at the expected array. It will first check if the item at the given index is an
250+
* array or {@link List}. If it is, it will check that against the expected array;
251+
* if it isn't, and varargs is true, it will attempt to compare the expected array
252+
* against all the elements of the given array starting from the given index.
253+
* @param startIdx inclusive start index
254+
* @param given the array the check is being performed on
255+
* @param expected the expected array
256+
* @param varargs whether to check for varargs
257+
* @param predicate the comparison predicate between the given and expected argument
258+
* @return true if it was a match
259+
*/
260+
private static boolean matchList(
261+
int startIdx,
262+
Object[] given,
263+
Object[] expected,
264+
boolean varargs,
265+
BiPredicate<Object, Object> predicate
266+
) {
267+
if(given.length <= startIdx) return false;
268+
if(given[startIdx] instanceof Object[]) {
269+
given = (Object[]) given[startIdx];
270+
startIdx = 0;
271+
} else if(given[startIdx] instanceof List<?>) {
272+
given = ((List<?>) given[startIdx]).toArray();
273+
startIdx = 0;
274+
} else if(!varargs) {
275+
return false;
276+
}
277+
278+
if(given.length - startIdx != expected.length) return false;
279+
for(; startIdx < expected.length; startIdx++) {
280+
if(!predicate.test(given[startIdx], expected[startIdx])) {
281+
return false;
282+
}
283+
}
284+
285+
return true;
286+
}
287+
288+
/**
289+
* Tests whether a given {@link AbstractInsnNode} matches the given opcode and arguments.
290+
* @param i the node to test
291+
* @param opcode the opcode to look for
292+
* @param args the arguments to look for
293+
* @return true if it was a match
294+
*/
295+
private static boolean matchNode(AbstractInsnNode i, int opcode, Object... args) {
296+
if(i.getOpcode() != opcode) return false;
297+
switch(i.getType()) {
298+
case AbstractInsnNode.INSN:
299+
return args.length == 0;
300+
case AbstractInsnNode.JUMP_INSN:
301+
JumpInsnNode jmp = (JumpInsnNode) i;
302+
return args.length == 1 && (
303+
jmp.label.getLabel().equals(args[0])
304+
|| jmp.label.equals(args[0])
305+
);
306+
case AbstractInsnNode.INVOKE_DYNAMIC_INSN: // why would you do this?
307+
if(args.length < 4) return false;
308+
InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode) i;
309+
return indy.name.equals(args[0])
310+
&& indy.desc.equals(args[1])
311+
&& indy.bsm.equals(args[2])
312+
&& matchList(3, args, indy.bsmArgs, true, Object::equals);
313+
case AbstractInsnNode.INT_INSN:
314+
return args.length == 1
315+
&& args[0] instanceof Integer
316+
&& ((IntInsnNode) i).operand == (Integer) args[0];
317+
case AbstractInsnNode.IINC_INSN:
318+
IincInsnNode iinc = (IincInsnNode) i;
319+
return args.length == 2
320+
&& args[0] instanceof Integer
321+
&& args[1] instanceof Integer
322+
&& iinc.var == (Integer) args[0]
323+
&& iinc.incr == (Integer) args[1];
324+
case AbstractInsnNode.LDC_INSN:
325+
return args.length == 1
326+
&& Objects.equals(((LdcInsnNode) i).cst, args[0]);
327+
case AbstractInsnNode.LOOKUPSWITCH_INSN:
328+
if(args.length < 3) return false;
329+
LookupSwitchInsnNode lookup = (LookupSwitchInsnNode) i;
330+
return (lookup.dflt.equals(args[0]) || lookup.dflt.getLabel().equals(args[0]))
331+
&& matchList(1, args, lookup.keys.toArray(), false, Object::equals)
332+
&& matchList(2, args, lookup.labels.toArray(), false, Object::equals);
333+
case AbstractInsnNode.MULTIANEWARRAY_INSN:
334+
MultiANewArrayInsnNode mana = (MultiANewArrayInsnNode) i;
335+
return args.length == 2 // TODO add proxy support
336+
&& mana.desc.equals(args[0])
337+
&& args[1] instanceof Integer
338+
&& mana.dims == (Integer) args[1];
339+
case AbstractInsnNode.METHOD_INSN:
340+
MethodInsnNode method = (MethodInsnNode) i;
341+
boolean methodMatch = true;
342+
switch(args.length) {
343+
case 2:
344+
methodMatch = args[1] instanceof Boolean
345+
&& method.itf == (Boolean) args[1];
346+
case 1:
347+
methodMatch &= args[0] instanceof MethodProxy;
348+
if(methodMatch) {
349+
MethodProxy proxy = (MethodProxy) args[0];
350+
return proxy.parent.internalName.equals(method.owner)
351+
&& proxy.name.equals(method.name)
352+
&& proxy.descriptor.equals(method.desc);
353+
} else break;
354+
case 4:
355+
methodMatch = args[3] instanceof Boolean
356+
&& method.itf == (Boolean) args[3];
357+
case 3:
358+
return methodMatch
359+
&& args[0].equals(method.owner)
360+
&& args[1].equals(method.desc)
361+
&& args[2].equals(method.name);
362+
}
363+
return false;
364+
case AbstractInsnNode.FIELD_INSN:
365+
FieldInsnNode field = (FieldInsnNode) i;
366+
if(args.length == 1 && args[0] instanceof FieldProxy) {
367+
FieldProxy proxy = (FieldProxy) args[0];
368+
return proxy.parent.internalName.equals(field.owner)
369+
&& proxy.name.equals(field.name)
370+
&& proxy.descriptor.equals(field.desc);
371+
} else if(args.length == 3) {
372+
return args[0].equals(field.owner)
373+
&& args[1].equals(field.name)
374+
&& args[2].equals(field.desc);
375+
} else return false;
376+
case AbstractInsnNode.TYPE_INSN:
377+
TypeInsnNode type = (TypeInsnNode) i;
378+
if(args.length != 1) return false;
379+
if(args[0] instanceof TypeProxy) {
380+
return ((TypeProxy) args[0]).internalName.equals(type.desc);
381+
} else return args[0].equals(type.desc);
382+
case AbstractInsnNode.TABLESWITCH_INSN:
383+
if(args.length < 4) return false;
384+
TableSwitchInsnNode tab = (TableSwitchInsnNode) i;
385+
BiPredicate<Object, Object> compareLabels = (p, ex) -> {
386+
LabelNode expected = (LabelNode) ex;
387+
return expected.equals(p) || expected.getLabel().equals(p);
388+
};
389+
return args[0] instanceof Integer
390+
&& tab.min == (Integer) args[0]
391+
&& args[1] instanceof Integer
392+
&& tab.min == (Integer) args[1]
393+
&& compareLabels.test(args[2], tab.dflt)
394+
&& matchList(3, args, tab.labels.toArray(), true, compareLabels);
395+
case AbstractInsnNode.VAR_INSN:
396+
return args.length == 1
397+
&& ((VarInsnNode) i).var == (Integer) args[0];
398+
default:
399+
return false;
400+
}
401+
}
402+
403+
/**
404+
* Tells the pattern matcher to ignore LABEL nodes.
225405
* @return the builder's state after the operation
226406
*/
227407
public Builder ignoreLabels() {
@@ -230,7 +410,7 @@ public Builder ignoreLabels() {
230410
}
231411

232412
/**
233-
* Tells the pattern matcher to ignore FRAME instructions.
413+
* Tells the pattern matcher to ignore FRAME nodes.
234414
* @return the builder's state after the operation
235415
*/
236416
public Builder ignoreFrames() {
@@ -239,12 +419,22 @@ public Builder ignoreFrames() {
239419
}
240420

241421
/**
242-
* Tells the pattern matcher to ignore LINENUMBER instructions.
422+
* Tells the pattern matcher to ignore LINENUMBER nodes.
243423
* @return the builder's state after the operation
244424
*/
245425
public Builder ignoreLineNumbers() {
246426
this.ignoreLineNumbers = true;
247427
return this;
248428
}
429+
430+
/**
431+
* Tells the pattern matcher to ignore all no-ops.
432+
* @return the builder's state after the operation
433+
*/
434+
public Builder ignoreNoOps() {
435+
return this.ignoreLabels()
436+
.ignoreFrames()
437+
.ignoreLineNumbers();
438+
}
249439
}
250440
}

0 commit comments

Comments
 (0)