/**
* JReversePro - Java Decompiler / Disassembler.
* Copyright (C) 2008 Karthik Kumar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. *
*/
package org.jreversepro.parser.instruction;
import org.jreversepro.jvm.JVMInstructionSet;
import org.jreversepro.jvm.Opcodes;
import org.jreversepro.reflect.instruction.Instruction;
import org.jreversepro.reflect.instruction.InstructionList;
public class DefaultInstructionListParser implements InstructionListParser {
/**
*
* This method is responsible for converting the bytecode stream of the method
* into JVM opcodes. By default all JVM opcodes are of fixed length in all
* contexts, except some. tableswitch and lookupswitch are basically variable
* length opcodes and their length primarily depend on the number of switch
* legs in the source code since they are one composite statement that
* contains information about all the possible targets.
*/
public InstructionList parseBytes(byte[] _bytecodes)
throws InstructionListParserException {
bytecodes = _bytecodes;
if (bytecodes == null) {
throw new InstructionListParserException("bytecodes are null");
}
InstructionList il = new InstructionList();
int maxCode = bytecodes.length;
int currentPc = 0;
int nextPc = -1;
int startPc = -1;
boolean bWide = false;
while (currentPc < maxCode) {
int curOpcode = Instruction.signedToUnsigned(bytecodes[currentPc]);
startPc = currentPc + 1;
if (curOpcode == Opcodes.OPCODE_TABLESWITCH) {
startPc = currentPc + 4 - (currentPc % 4);
nextPc = parseTableSwitchInstruction(startPc);
} else if (curOpcode == Opcodes.OPCODE_LOOKUPSWITCH) {
startPc = currentPc + 4 - (currentPc % 4);
nextPc = parseLookupSwitchInstruction(startPc);
} else {
nextPc = currentPc
+ JVMInstructionSet.getOpcodeLength(curOpcode, bWide);
}
Instruction ins = new Instruction(currentPc, curOpcode, getArgArray(
startPc, nextPc), nextPc, bWide);
il.add(ins);
bWide = (curOpcode == Opcodes.OPCODE_WIDE);
currentPc = nextPc;
}
return il;
}
/**
* This method returns the length of the variable instruction tableswitch.
*
* @param index
* beginning index of the tableswitch statement into the bytecode
* stream of the method. The format is as follows. The first 4 bytes
* are for defaultbyte. The next 4 for the lowest value in the case
* leg values. The next 4 for the highest value in the case high
* values. Then we will have ( high - low ) * 4 bytes - all
* containing the target index to be jumped into relative to the
* index of the current switch instruction.
* @return Returns the integer that is Index + length of the instruction so
* that the next JVM opcode can proceed from there.
*/
private int parseTableSwitchInstruction(int index) {
// Read Default Byte
index += 4;
int lowByte = retrieveTargetPc(index);
index += 4;
int highByte = retrieveTargetPc(index);
index += 4;
index += (4 * (highByte - lowByte + 1));
return index;
}
/**
* This method returns the length of the variable instruction lookupswitch.
*
* @param index
* beginning index of the lookupswitch statement into the bytecode
* stream of the method. The format is as follows. The first 4 bytes
* are for defaultbyte. The next 4 for the number of pairs of ( case
* leg value, target) that will appear as follows, say N. Then there
* will be (2 * N) * 4 bytes , ( N pairs of integers ), with each
* pair containing the case leg value first and the second integer
* letting us know the target of the case leg relative to the index
* of the method.
* @return Returns the integer that is Index + length of the instruction so
* that the next JVM opcode can proceed from there.
*/
private int parseLookupSwitchInstruction(int index) {
// Read Default Byte
index += 4;
int nPairs = retrieveTargetPc(index);
index += 4;
index += (8 * nPairs);
return index;
}
/**
* This method is used to return the signed integer value of the next four
* bytes, if given an bytecode index,
*
* @param newIndex
* Index from which the integer array starts. This is in big-endian
* order.
* @return Returns the integer value.
*/
private int retrieveTargetPc(int newIndex) {
int result = 0;
for (int i = 0; i < 4; i++) {
int thisByte = Instruction.signedToUnsigned(bytecodes[newIndex + i]);
result += (int) (Math.pow(256, 3 - i)) * thisByte;
}
if (result < 0) {
return result + 256;
} else {
return result;
}
}
/**
* Given the start index and the end index this method returns a byte array
* that contain the byte values with the start array value included and end
* array excluded.
*
* @param start
* start index into the byte array.
* @param end
* end index into the byte array.
* @return Returns a byte array containing the bytes from start to end , end
* exclusive and start inclusive.
*/
private byte[] getArgArray(int start, int end) {
byte[] result = null;
if (start < end) {
result = new byte[end - start];
for (int i = start; i < end; i++) {
result[i - start] = bytecodes[i];
}
}
return result;
}
private byte[] bytecodes;
}