/* * Bytecode Analysis Framework * Copyright (C) 2003,2004 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba; import edu.umd.cs.findbugs.SystemProperties; /** * Scan the raw bytecodes of a method. * This is useful in order to find out quickly whether or not * a method uses particular instructions. * * @author David Hovemeyer */ public class BytecodeScanner implements org.apache.bcel.Constants { private static final boolean DEBUG = SystemProperties.getBoolean("bs.debug"); /** * Callback interface to report scanned instructions. */ public interface Callback { /** * Called to indicate that a particular bytecode has been scanned. * * @param opcode the opcode of the instruction * @param index the bytecode offset of the instruction */ public void handleInstruction(int opcode, int index); } /** * Convert the unsigned value of a byte into a short. * * @param value the byte * @return the byte's unsigned value as a short */ private static short unsignedValueOf(byte value) { short result; if ((value & 0x80) != 0) { result = (short) (value & 0x7F); result |= 0x80; } else { result = value; } return result; } /** * Extract an int from bytes at the given offset in the array. * * @param arr the array * @param offset the offset in the array */ private static int extractInt(byte[] arr, int offset) { return ((arr[offset] & 0xFF) << 24) | ((arr[offset + 1] & 0xFF) << 16) | ((arr[offset + 2] & 0xFF) << 8) | (arr[offset + 3] & 0xFF); } private static final int PAD[] = {0, 3, 2, 1}; /** * Scan the raw bytecodes of a method. * * @param instructionList the bytecodes * @param callback the callback object */ public void scan(byte[] instructionList, Callback callback) { boolean wide = false; for (int index = 0; index < instructionList.length;) { short opcode = unsignedValueOf(instructionList[index]); callback.handleInstruction(opcode, index); if (DEBUG) System.out.println(index + ": " + OPCODE_NAMES[opcode]); switch (opcode) { // Single byte instructions. case NOP: case ACONST_NULL: case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: case LCONST_0: case LCONST_1: case FCONST_0: case FCONST_1: case FCONST_2: case DCONST_0: case DCONST_1: case ILOAD_0: case ILOAD_1: case ILOAD_2: case ILOAD_3: case LLOAD_0: case LLOAD_1: case LLOAD_2: case LLOAD_3: case FLOAD_0: case FLOAD_1: case FLOAD_2: case FLOAD_3: case DLOAD_0: case DLOAD_1: case DLOAD_2: case DLOAD_3: case ALOAD_0: case ALOAD_1: case ALOAD_2: case ALOAD_3: case IALOAD: case LALOAD: case FALOAD: case DALOAD: case AALOAD: case BALOAD: case CALOAD: case SALOAD: case ISTORE_0: case ISTORE_1: case ISTORE_2: case ISTORE_3: case LSTORE_0: case LSTORE_1: case LSTORE_2: case LSTORE_3: case FSTORE_0: case FSTORE_1: case FSTORE_2: case FSTORE_3: case DSTORE_0: case DSTORE_1: case DSTORE_2: case DSTORE_3: case ASTORE_0: case ASTORE_1: case ASTORE_2: case ASTORE_3: case IASTORE: case LASTORE: case FASTORE: case DASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: case POP: case POP2: case DUP: case DUP_X1: case DUP_X2: case DUP2: case DUP2_X1: case DUP2_X2: case SWAP: case IADD: case LADD: case FADD: case DADD: case ISUB: case LSUB: case FSUB: case DSUB: case IMUL: case LMUL: case FMUL: case DMUL: case IDIV: case LDIV: case FDIV: case DDIV: case IREM: case LREM: case FREM: case DREM: case INEG: case LNEG: case FNEG: case DNEG: case ISHL: case LSHL: case ISHR: case LSHR: case IUSHR: case LUSHR: case IAND: case LAND: case IOR: case LOR: case IXOR: case LXOR: case I2L: case I2F: case I2D: case L2I: case L2F: case L2D: case F2I: case F2L: case F2D: case D2I: case D2L: case D2F: case I2B: case I2C: case I2S: case LCMP: case FCMPL: case FCMPG: case DCMPL: case DCMPG: case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: case RETURN: case ARRAYLENGTH: case ATHROW: case MONITORENTER: case MONITOREXIT: ++index; break; // Two byte instructions. case BIPUSH: case LDC: case NEWARRAY: index += 2; break; // Instructions that can be used with the WIDE prefix. case ILOAD: case LLOAD: case FLOAD: case DLOAD: case ALOAD: case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case RET: if (wide) { // Skip opcode and two immediate bytes. index += 3; wide = false; } else { // Skip opcode and one immediate byte. index += 2; } break; // IINC is a special case for WIDE handling case IINC: if (wide) { // Skip opcode, two byte index, and two byte immediate value. index += 5; wide = false; } else { // Skip opcode, one byte index, and one byte immedate value. index += 3; } break; // Three byte instructions. case SIPUSH: case LDC_W: case LDC2_W: case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: case GOTO: case JSR: case GETSTATIC: case PUTSTATIC: case GETFIELD: case PUTFIELD: case INVOKEVIRTUAL: case INVOKESPECIAL: case INVOKESTATIC: case NEW: case ANEWARRAY: case CHECKCAST: case INSTANCEOF: case IFNULL: case IFNONNULL: index += 3; break; // Four byte instructions. case MULTIANEWARRAY: index += 4; break; // Five byte instructions. case INVOKEINTERFACE: case GOTO_W: case JSR_W: index += 5; break; // TABLESWITCH - variable length. case TABLESWITCH: { // Skip padding. int offset = index + 1; // skip the opcode offset += PAD[offset & 3]; assert (offset & 3) == 0; // offset should now be posited at the default value // Extract min and max values. int low = extractInt(instructionList, offset + 4); int high = extractInt(instructionList, offset + 8); int tableSize = (high - low) + 1; if (DEBUG) System.out.println("tableswitch: low=" + low + ", high=" + high + ", tableSize=" + tableSize); // Skip to next instruction. index = offset + 12 + (tableSize * 4); } break; // LOOKUPSWITCH - variable length. case LOOKUPSWITCH: { // Skip padding. int offset = index + 1; // skip the opcode offset += PAD[offset & 3]; assert (offset & 3) == 0; // offset should now be posited at the default value // Extract number of value/offset pairs. int numPairs = extractInt(instructionList, offset + 4); if (DEBUG) System.out.println("lookupswitch: numPairs=" + numPairs); // Skip to next instruction. index = offset + 8 + (numPairs * 8); } break; // Wide prefix. case WIDE: wide = true; ++index; break; default: throw new IllegalArgumentException("Bad opcode " + opcode + " at offset " + index); } if (index < 0) throw new IllegalStateException("index=" + index + ", opcode=" + opcode); } } } // vim:ts=4