/*
 * 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 INVOKEDYNAMIC:
            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);
            }

        }
    }
}

