package org.openrs2.asm import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.InsnNode import org.objectweb.asm.tree.IntInsnNode import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.LabelNode import org.objectweb.asm.tree.LdcInsnNode import org.objectweb.asm.tree.LookupSwitchInsnNode import org.objectweb.asm.tree.TableSwitchInsnNode import org.objectweb.asm.tree.TryCatchBlockNode import org.objectweb.asm.util.Textifier import org.objectweb.asm.util.TraceMethodVisitor import java.io.PrintWriter import java.io.StringWriter private val PURE_OPCODES = setOf( -1, 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.BIPUSH, Opcodes.SIPUSH, Opcodes.LDC, Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD, 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, /* * XXX(gpe): strictly speaking the *DEV and *REM instructions have side * effects (unless we can prove that the second argument is non-zero). * However, treating them as having side effects reduces the number of * dummy variables we can remove, so we pretend they don't have any side * effects. * * This doesn't seem to cause any problems with the client, as it doesn't * deliberately try to trigger divide-by-zero exceptions. */ 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.GETSTATIC, Opcodes.NEW, Opcodes.INSTANCEOF ) private val IMPURE_OPCODES = setOf( Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE, Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, Opcodes.SASTORE, Opcodes.IINC, 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.RET, Opcodes.TABLESWITCH, Opcodes.LOOKUPSWITCH, Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN, Opcodes.RETURN, Opcodes.PUTSTATIC, Opcodes.GETFIELD, Opcodes.PUTFIELD, Opcodes.INVOKEVIRTUAL, Opcodes.INVOKESPECIAL, Opcodes.INVOKESTATIC, Opcodes.INVOKEINTERFACE, Opcodes.INVOKEDYNAMIC, Opcodes.NEWARRAY, Opcodes.ANEWARRAY, Opcodes.ARRAYLENGTH, Opcodes.ATHROW, Opcodes.CHECKCAST, Opcodes.MONITORENTER, Opcodes.MONITOREXIT, Opcodes.MULTIANEWARRAY, Opcodes.IFNULL, Opcodes.IFNONNULL ) private val THROW_RETURN_OPCODES = listOf( Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN, Opcodes.RETURN, Opcodes.RET, Opcodes.ATHROW ) public val AbstractInsnNode.nextReal: AbstractInsnNode? get() { var insn = next while (insn != null && insn.opcode == -1) { insn = insn.next } return insn } public val AbstractInsnNode.previousReal: AbstractInsnNode? get() { var insn = previous while (insn != null && insn.opcode == -1) { insn = insn.previous } return insn } public val AbstractInsnNode.nextVirtual: AbstractInsnNode? get() { var insn = next while (insn != null && insn.opcode != -1) { insn = insn.next } return insn } public val AbstractInsnNode.previousVirtual: AbstractInsnNode? get() { var insn = previous while (insn != null && insn.opcode != -1) { insn = insn.previous } return insn } public val AbstractInsnNode.intConstant: Int? get() = when (this) { is IntInsnNode -> { if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) { operand } else { null } } is LdcInsnNode -> { val cst = cst if (cst is Int) { cst } else { null } } else -> when (opcode) { Opcodes.ICONST_M1 -> -1 Opcodes.ICONST_0 -> 0 Opcodes.ICONST_1 -> 1 Opcodes.ICONST_2 -> 2 Opcodes.ICONST_3 -> 3 Opcodes.ICONST_4 -> 4 Opcodes.ICONST_5 -> 5 else -> null } } public val AbstractInsnNode.isSequential: Boolean get() = when (this) { is LabelNode -> false is JumpInsnNode -> false is TableSwitchInsnNode -> false is LookupSwitchInsnNode -> false else -> opcode !in THROW_RETURN_OPCODES } public val AbstractInsnNode.isPure: Boolean get() = when (opcode) { in PURE_OPCODES -> true in IMPURE_OPCODES -> false else -> throw IllegalArgumentException() } public fun Int.toAbstractInsnNode(): AbstractInsnNode = when (this) { -1 -> InsnNode(Opcodes.ICONST_M1) 0 -> InsnNode(Opcodes.ICONST_0) 1 -> InsnNode(Opcodes.ICONST_1) 2 -> InsnNode(Opcodes.ICONST_2) 3 -> InsnNode(Opcodes.ICONST_3) 4 -> InsnNode(Opcodes.ICONST_4) 5 -> InsnNode(Opcodes.ICONST_5) in Byte.MIN_VALUE..Byte.MAX_VALUE -> IntInsnNode(Opcodes.BIPUSH, this) in Short.MIN_VALUE..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, this) else -> LdcInsnNode(this) } public fun AbstractInsnNode.toPrettyString(): String { val printer = Textifier() val visitor = TraceMethodVisitor(printer) accept(visitor) StringWriter().use { stringWriter -> PrintWriter(stringWriter).use { printWriter -> printer.print(printWriter) return stringWriter.toString().trim() } } } public fun TryCatchBlockNode.isBodyEmpty(): Boolean { var current = start.next while (true) { when { current == null -> error("Failed to reach end of try-catch block.") current === end -> return true current.opcode != -1 -> return false else -> current = current.next } } }