/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later, * or the Apache License Version 2.0. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. */ package io.github.proxyhotswap.javassist.bytecode; import io.github.proxyhotswap.javassist.CtMethod; import java.io.PrintStream; /** * Simple utility class for printing the bytecode instructions of a method. * * @author Jason T. Greene */ public class InstructionPrinter implements Opcode { private final static String opcodes[] = Mnemonic.OPCODE; private final PrintStream stream; /** * Constructs a <code>InstructionPrinter</code> object. */ public InstructionPrinter(PrintStream stream) { this.stream = stream; } /** * Prints the bytecode instructions of a given method. */ public static void print(CtMethod method, PrintStream stream) { (new InstructionPrinter(stream)).print(method); } /** * Prints the bytecode instructions of a given method. */ public void print(CtMethod method) { MethodInfo info = method.getMethodInfo2(); ConstPool pool = info.getConstPool(); CodeAttribute code = info.getCodeAttribute(); if (code == null) return; CodeIterator iterator = code.iterator(); while (iterator.hasNext()) { int pos; try { pos = iterator.next(); } catch (BadBytecode e) { throw new RuntimeException(e); } stream.println(pos + ": " + instructionString(iterator, pos, pool)); } } /** * Gets a string representation of the bytecode instruction at the specified * position. */ public static String instructionString(CodeIterator iter, int pos, ConstPool pool) { int opcode = iter.byteAt(pos); if (opcode > opcodes.length || opcode < 0) throw new IllegalArgumentException("Invalid opcode, opcode: " + opcode + " pos: "+ pos); String opstring = opcodes[opcode]; switch (opcode) { case BIPUSH: return opstring + " " + iter.byteAt(pos + 1); case SIPUSH: return opstring + " " + iter.s16bitAt(pos + 1); case LDC: return opstring + " " + ldc(pool, iter.byteAt(pos + 1)); case LDC_W : case LDC2_W : return opstring + " " + ldc(pool, iter.u16bitAt(pos + 1)); case ILOAD: case LLOAD: case FLOAD: case DLOAD: case ALOAD: case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: return opstring + " " + iter.byteAt(pos + 1); case IFEQ: case IFGE: case IFGT: case IFLE: case IFLT: case IFNE: case IFNONNULL: case IFNULL: case IF_ACMPEQ: case IF_ACMPNE: case IF_ICMPEQ: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPNE: return opstring + " " + (iter.s16bitAt(pos + 1) + pos); case IINC: return opstring + " " + iter.byteAt(pos + 1); case GOTO: case JSR: return opstring + " " + (iter.s16bitAt(pos + 1) + pos); case RET: return opstring + " " + iter.byteAt(pos + 1); case TABLESWITCH: return tableSwitch(iter, pos); case LOOKUPSWITCH: return lookupSwitch(iter, pos); case GETSTATIC: case PUTSTATIC: case GETFIELD: case PUTFIELD: return opstring + " " + fieldInfo(pool, iter.u16bitAt(pos + 1)); case INVOKEVIRTUAL: case INVOKESPECIAL: case INVOKESTATIC: return opstring + " " + methodInfo(pool, iter.u16bitAt(pos + 1)); case INVOKEINTERFACE: return opstring + " " + interfaceMethodInfo(pool, iter.u16bitAt(pos + 1)); case INVOKEDYNAMIC: return opstring + " " + iter.u16bitAt(pos + 1); case NEW: return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); case NEWARRAY: return opstring + " " + arrayInfo(iter.byteAt(pos + 1)); case ANEWARRAY: case CHECKCAST: return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); case WIDE: return wide(iter, pos); case MULTIANEWARRAY: return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); case GOTO_W: case JSR_W: return opstring + " " + (iter.s32bitAt(pos + 1)+ pos); default: return opstring; } } private static String wide(CodeIterator iter, int pos) { int opcode = iter.byteAt(pos + 1); int index = iter.u16bitAt(pos + 2); switch (opcode) { case ILOAD: case LLOAD: case FLOAD: case DLOAD: case ALOAD: case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case IINC: case RET: return opcodes[opcode] + " " + index; default: throw new RuntimeException("Invalid WIDE operand"); } } private static String arrayInfo(int type) { switch (type) { case T_BOOLEAN: return "boolean"; case T_CHAR: return "char"; case T_BYTE: return "byte"; case T_SHORT: return "short"; case T_INT: return "int"; case T_LONG: return "long"; case T_FLOAT: return "float"; case T_DOUBLE: return "double"; default: throw new RuntimeException("Invalid array type"); } } private static String classInfo(ConstPool pool, int index) { return "#" + index + " = Class " + pool.getClassInfo(index); } private static String interfaceMethodInfo(ConstPool pool, int index) { return "#" + index + " = Method " + pool.getInterfaceMethodrefClassName(index) + "." + pool.getInterfaceMethodrefName(index) + "(" + pool.getInterfaceMethodrefType(index) + ")"; } private static String methodInfo(ConstPool pool, int index) { return "#" + index + " = Method " + pool.getMethodrefClassName(index) + "." + pool.getMethodrefName(index) + "(" + pool.getMethodrefType(index) + ")"; } private static String fieldInfo(ConstPool pool, int index) { return "#" + index + " = Field " + pool.getFieldrefClassName(index) + "." + pool.getFieldrefName(index) + "(" + pool.getFieldrefType(index) + ")"; } private static String lookupSwitch(CodeIterator iter, int pos) { StringBuffer buffer = new StringBuffer("lookupswitch {\n"); int index = (pos & ~3) + 4; // default buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n"); int npairs = iter.s32bitAt(index += 4); int end = npairs * 8 + (index += 4); for (; index < end; index += 8) { int match = iter.s32bitAt(index); int target = iter.s32bitAt(index + 4) + pos; buffer.append("\t\t").append(match).append(": ").append(target).append("\n"); } buffer.setCharAt(buffer.length() - 1, '}'); return buffer.toString(); } private static String tableSwitch(CodeIterator iter, int pos) { StringBuffer buffer = new StringBuffer("tableswitch {\n"); int index = (pos & ~3) + 4; // default buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n"); int low = iter.s32bitAt(index += 4); int high = iter.s32bitAt(index += 4); int end = (high - low + 1) * 4 + (index += 4); // Offset table for (int key = low; index < end; index += 4, key++) { int target = iter.s32bitAt(index) + pos; buffer.append("\t\t").append(key).append(": ").append(target).append("\n"); } buffer.setCharAt(buffer.length() - 1, '}'); return buffer.toString(); } private static String ldc(ConstPool pool, int index) { int tag = pool.getTag(index); switch (tag) { case ConstPool.CONST_String: return "#" + index + " = \"" + pool.getStringInfo(index) + "\""; case ConstPool.CONST_Integer: return "#" + index + " = int " + pool.getIntegerInfo(index); case ConstPool.CONST_Float: return "#" + index + " = float " + pool.getFloatInfo(index); case ConstPool.CONST_Long: return "#" + index + " = long " + pool.getLongInfo(index); case ConstPool.CONST_Double: return "#" + index + " = int " + pool.getDoubleInfo(index); case ConstPool.CONST_Class: return classInfo(pool, index); default: throw new RuntimeException("bad LDC: " + tag); } } }