1
1
package ftbsc .lll .utils ;
2
2
3
3
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 .*;
6
8
7
9
import java .util .ArrayList ;
8
10
import java .util .List ;
11
+ import java .util .Objects ;
12
+ import java .util .function .BiPredicate ;
9
13
import java .util .function .Predicate ;
10
14
11
15
/**
@@ -68,7 +72,7 @@ public static Builder builder() {
68
72
* @return the InsnSequence object representing the matched pattern
69
73
*/
70
74
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 ());
72
76
}
73
77
74
78
/**
@@ -79,19 +83,19 @@ public InsnSequence find(MethodNode node) {
79
83
public InsnSequence find (AbstractInsnNode node ) {
80
84
if (node != null ) {
81
85
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
84
88
first = cur ;
85
89
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 ()) {
87
91
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 ;
91
95
}
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
95
99
else return new InsnSequence (first , last );
96
100
} else match ++;
97
101
}
@@ -153,7 +157,7 @@ public Builder reverse() {
153
157
* @return the builder's state after the operation
154
158
*/
155
159
public Builder check (Predicate <AbstractInsnNode > predicate ) {
156
- predicates .add (predicate );
160
+ this . predicates .add (predicate );
157
161
return this ;
158
162
}
159
163
@@ -162,7 +166,7 @@ public Builder check(Predicate<AbstractInsnNode> predicate) {
162
166
* @return the builder's state after the operation
163
167
*/
164
168
public Builder any () {
165
- return check (i -> true );
169
+ return this . check (i -> true );
166
170
}
167
171
168
172
/**
@@ -171,7 +175,7 @@ public Builder any() {
171
175
* @return the builder's state after the operation
172
176
*/
173
177
public Builder opcode (int opcode ) {
174
- return check (i -> i .getOpcode () == opcode );
178
+ return this . check (i -> i .getOpcode () == opcode );
175
179
}
176
180
177
181
/**
@@ -181,47 +185,223 @@ public Builder opcode(int opcode) {
181
185
*/
182
186
public Builder opcodes (int ... opcodes ) {
183
187
Builder res = this ;
184
- for (int o : opcodes )
185
- res = opcode (o );
188
+ for (int o : opcodes ) {
189
+ res = this .opcode (o );
190
+ }
191
+
186
192
return res ;
187
193
}
188
194
189
195
/**
190
- * Matches a method invokation of any kind: one of INVOKEVIRTUAL,
196
+ * Matches a method invocation of any kind: one of INVOKEVIRTUAL,
191
197
* INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
192
198
* @return the builder's state after the operation
193
199
*/
194
200
public Builder method () {
195
- return check (i -> i .getType () == AbstractInsnNode .METHOD_INSN );
201
+ return this . check (i -> i .getType () == AbstractInsnNode .METHOD_INSN );
196
202
}
197
203
198
204
/**
199
- * Matches a field invokation of any kind: one of GETSTATIC, PUTSTATIC,
205
+ * Matches a field invocation of any kind: one of GETSTATIC, PUTSTATIC,
200
206
* GETFIELD or PUTFIELD.
201
207
* @return the builder's state after the operation
202
208
*/
203
209
public Builder field () {
204
- return check (i -> i .getType () == AbstractInsnNode .FIELD_INSN );
210
+ return this . check (i -> i .getType () == AbstractInsnNode .FIELD_INSN );
205
211
}
206
212
207
213
/**
208
214
* Matches any kind of jump instruction.
209
215
* @return the builder's state after the operation
210
216
*/
211
217
public Builder jump () {
212
- return check (i -> i .getType () == AbstractInsnNode .JUMP_INSN );
218
+ return this . check (i -> i .getType () == AbstractInsnNode .JUMP_INSN );
213
219
}
214
220
215
221
/**
216
222
* Matches any kind of label.
217
223
* @return the builder's state after the operation
218
224
*/
219
225
public Builder label () {
220
- return check (i -> i .getType () == AbstractInsnNode .LABEL );
226
+ return this . check (i -> i .getType () == AbstractInsnNode .LABEL );
221
227
}
222
228
223
229
/**
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.
225
405
* @return the builder's state after the operation
226
406
*/
227
407
public Builder ignoreLabels () {
@@ -230,7 +410,7 @@ public Builder ignoreLabels() {
230
410
}
231
411
232
412
/**
233
- * Tells the pattern matcher to ignore FRAME instructions .
413
+ * Tells the pattern matcher to ignore FRAME nodes .
234
414
* @return the builder's state after the operation
235
415
*/
236
416
public Builder ignoreFrames () {
@@ -239,12 +419,22 @@ public Builder ignoreFrames() {
239
419
}
240
420
241
421
/**
242
- * Tells the pattern matcher to ignore LINENUMBER instructions .
422
+ * Tells the pattern matcher to ignore LINENUMBER nodes .
243
423
* @return the builder's state after the operation
244
424
*/
245
425
public Builder ignoreLineNumbers () {
246
426
this .ignoreLineNumbers = true ;
247
427
return this ;
248
428
}
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
+ }
249
439
}
250
440
}
0 commit comments