diff options
author | zaaarf <zaaarf@proton.me> | 2023-02-07 14:20:26 +0100 |
---|---|---|
committer | zaaarf <zaaarf@proton.me> | 2023-02-07 14:20:26 +0100 |
commit | 764ff58eaa6fbfbf3e3809e67c23fcccacfa68eb (patch) | |
tree | cff32928c0b36b7b899fe44d549478007e16daf6 /src/main/java/ftbsc/lll/tools/PatternMatcher.java | |
parent | 809939c50264758ec4f30d58c2557f4f876f9b46 (diff) |
feat: implemented ASM pattern matcher
Diffstat (limited to 'src/main/java/ftbsc/lll/tools/PatternMatcher.java')
-rw-r--r-- | src/main/java/ftbsc/lll/tools/PatternMatcher.java | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/main/java/ftbsc/lll/tools/PatternMatcher.java b/src/main/java/ftbsc/lll/tools/PatternMatcher.java new file mode 100644 index 0000000..04631fd --- /dev/null +++ b/src/main/java/ftbsc/lll/tools/PatternMatcher.java @@ -0,0 +1,242 @@ +package ftbsc.lll.tools; + +import ftbsc.lll.exception.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 PatternMatcher + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Tries to match the given pattern on a given MethodNode. + * @param node the 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 InsnSequence object representing the matched pattern + */ + public InsnSequence find(AbstractInsnNode node) { + if(node != null) { + int match = 0; + AbstractInsnNode first = null; + AbstractInsnNode last = null; + for(AbstractInsnNode cur = node; cur != null; cur = reverse ? cur.getPrevious() : cur.getNext()) { + if(ignoreLabels && cur.getType() == AbstractInsnNode.LABEL) continue; + if(ignoreFrames && cur.getType() == AbstractInsnNode.FRAME) continue; + if(ignoreLineNumbers && cur.getType() == AbstractInsnNode.LINE) continue; + if(predicates.get(match).test(cur)) { + match++; + if(first == null) + first = cur; + } else if(match == predicates.size()) { + last = cur.getPrevious(); //it was actually the preiovus run in this case + break; + } else break; + } + if(first != null && last != null) + return new InsnSequence(first, last); + } + throw new PatternNotFoundException("Failed to find pattern!"); + } + + /** + * The Builder object for PatternMatcher. + */ + private 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 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); + } + + /** + * 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; + } + } +} |