/*
 * 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
