package dev.openrs2.asm; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Stream; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.util.Printer; public final class InsnMatcher { private static final int PRIVATE_USE_AREA = 0xE000; private static final Map OPCODE_GROUPS = Map.ofEntries( Map.entry("InsnNode", new int[] { Opcodes.NOP, Opcodes.ACONST_NULL, Opcodes.ICONST_M1, Opcodes.ICONST_0, Opcodes.ICONST_1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5, Opcodes.LCONST_0, Opcodes.LCONST_1, Opcodes.FCONST_0, Opcodes.FCONST_1, Opcodes.FCONST_2, Opcodes.DCONST_0, Opcodes.DCONST_1, Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, Opcodes.SASTORE, Opcodes.POP, Opcodes.POP2, Opcodes.DUP, Opcodes.DUP_X1, Opcodes.DUP_X2, Opcodes.DUP2, Opcodes.DUP2_X1, Opcodes.DUP2_X2, Opcodes.SWAP, Opcodes.IADD, Opcodes.LADD, Opcodes.FADD, Opcodes.DADD, Opcodes.ISUB, Opcodes.LSUB, Opcodes.FSUB, Opcodes.DSUB, Opcodes.IMUL, Opcodes.LMUL, Opcodes.FMUL, Opcodes.DMUL, Opcodes.IDIV, Opcodes.LDIV, Opcodes.FDIV, Opcodes.DDIV, Opcodes.IREM, Opcodes.LREM, Opcodes.FREM, Opcodes.DREM, Opcodes.INEG, Opcodes.LNEG, Opcodes.FNEG, Opcodes.DNEG, Opcodes.ISHL, Opcodes.LSHL, Opcodes.ISHR, Opcodes.LSHR, Opcodes.IUSHR, Opcodes.LUSHR, Opcodes.IAND, Opcodes.LAND, Opcodes.IOR, Opcodes.LOR, Opcodes.IXOR, Opcodes.LXOR, Opcodes.I2L, Opcodes.I2F, Opcodes.I2D, Opcodes.L2I, Opcodes.L2F, Opcodes.L2D, Opcodes.F2I, Opcodes.F2L, Opcodes.F2D, Opcodes.D2I, Opcodes.D2L, Opcodes.D2F, Opcodes.I2B, Opcodes.I2C, Opcodes.I2S, Opcodes.LCMP, Opcodes.FCMPL, Opcodes.FCMPG, Opcodes.DCMPL, Opcodes.DCMPG, Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN, Opcodes.RETURN, Opcodes.ARRAYLENGTH, Opcodes.ATHROW, Opcodes.MONITORENTER, Opcodes.MONITOREXIT }), Map.entry("IntInsnNode", new int[] { Opcodes.BIPUSH, Opcodes.SIPUSH, Opcodes.NEWARRAY }), Map.entry("VarInsnNode", new int[] { Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD, Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE, Opcodes.RET }), Map.entry("TypeInsnNode", new int[] { Opcodes.NEW, Opcodes.ANEWARRAY, Opcodes.CHECKCAST, Opcodes.INSTANCEOF }), Map.entry("FieldInsnNode", new int[] { Opcodes.GETSTATIC, Opcodes.PUTSTATIC, Opcodes.GETFIELD, Opcodes.PUTFIELD }), Map.entry("MethodInsnNode", new int[] { Opcodes.INVOKEVIRTUAL, Opcodes.INVOKESPECIAL, Opcodes.INVOKESTATIC, Opcodes.INVOKEINTERFACE }), Map.entry("InvokeDynamicInsnNode", new int[] { Opcodes.INVOKEDYNAMIC }), Map.entry("JumpInsnNode", new int[] { Opcodes.IFEQ, Opcodes.IFNE, Opcodes.IFLT, Opcodes.IFGE, Opcodes.IFGT, Opcodes.IFLE, Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPLT, Opcodes.IF_ICMPGE, Opcodes.IF_ICMPGT, Opcodes.IF_ICMPLE, Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE, Opcodes.GOTO, Opcodes.JSR, Opcodes.IFNULL, Opcodes.IFNONNULL }), Map.entry("LdcInsnNode", new int[] { Opcodes.LDC }), Map.entry("IincInsnNode", new int[] { Opcodes.IINC }), Map.entry("TableSwitchInsnNode", new int[] { Opcodes.TABLESWITCH }), Map.entry("LookupSwitchInsnNode", new int[] { Opcodes.LOOKUPSWITCH }), Map.entry("MultiANewArrayInsnNode", new int[] { Opcodes.MULTIANEWARRAY }), Map.entry("ICONST", new int[] { Opcodes.ICONST_M1, Opcodes.ICONST_0, Opcodes.ICONST_1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5 }) ); private static char opcodeToCodepoint(int opcode) { return (char) (PRIVATE_USE_AREA + opcode); } private static List createRealInsnList(InsnList list) { List realInsns = new ArrayList<>(); for (var insn = list.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != -1) { realInsns.add(insn); } } return realInsns; } private static String createCodepointSeq(List insns) { var codepoints = new char[insns.size()]; for (var i = 0; i < codepoints.length; i++) { codepoints[i] = opcodeToCodepoint(insns.get(i).getOpcode()); } return new String(codepoints); } public static InsnMatcher compile(String expr) { var pattern = new StringBuilder(); var opcode = new StringBuilder(); for (var i = 0; i < expr.length(); i++) { var c = expr.charAt(i); if (Character.isLetterOrDigit(c) || c == '_') { opcode.append(c); } else { if (opcode.length() > 0) { appendOpcodeRegex(pattern, opcode.toString()); opcode.delete(0, opcode.length()); } if (!Character.isWhitespace(c)) { pattern.append(c); } } } if (opcode.length() > 0) { appendOpcodeRegex(pattern, opcode.toString()); } return new InsnMatcher(Pattern.compile(pattern.toString())); } private static void appendOpcodeRegex(StringBuilder pattern, String opcode) { for (var i = 0; i < Printer.OPCODES.length; i++) { if (opcode.equals(Printer.OPCODES[i])) { pattern.append(opcodeToCodepoint(i)); return; } } var group = OPCODE_GROUPS.get(opcode); if (group != null) { pattern.append('('); for (var i = 0; i < group.length; i++) { pattern.append(opcodeToCodepoint(group[i])); if (i != group.length - 1) { pattern.append('|'); } } pattern.append(')'); return; } if (opcode.equals("AbstractInsnNode")) { pattern.append('.'); return; } throw new IllegalArgumentException(opcode + " is not a valid opcode or opcode group"); } private final Pattern pattern; private InsnMatcher(Pattern pattern) { this.pattern = pattern; } public Stream> match(MethodNode method) { return match(method.instructions); } public Stream> match(InsnList list) { Stream.Builder> matches = Stream.builder(); var insns = createRealInsnList(list); var matcher = pattern.matcher(createCodepointSeq(insns)); while (matcher.find()) { var start = matcher.start(); var end = matcher.end(); matches.add(Collections.unmodifiableList(insns.subList(start, end))); } return matches.build(); } }