527 lines
14 KiB
Java
527 lines
14 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2002 - 2006 IBM Corporation.
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial API and implementation
|
|
*******************************************************************************/
|
|
|
|
package com.ibm.wala.util.bytecode;
|
|
|
|
import com.ibm.wala.classLoader.IClass;
|
|
import com.ibm.wala.classLoader.IMethod;
|
|
import com.ibm.wala.shrikeBT.BytecodeConstants;
|
|
|
|
/**
|
|
* Provides minimal abstraction layer to a stream of bytecodes from the code attribute of a method.
|
|
*/
|
|
public class BytecodeStream implements BytecodeConstants {
|
|
private final IMethod method;
|
|
|
|
private final IClass declaringClass;
|
|
|
|
private final int bcLength;
|
|
|
|
private final byte[] bcodes;
|
|
|
|
private int bcIndex;
|
|
|
|
private int opcode;
|
|
|
|
private boolean wide;
|
|
|
|
/**
|
|
* @param m the method containing the bytecodes
|
|
* @param bc the array of bytecodes
|
|
* @throws IllegalArgumentException if bc is null
|
|
* @throws IllegalArgumentException if m is null
|
|
*/
|
|
public BytecodeStream(IMethod m, byte[] bc) {
|
|
if (m == null) {
|
|
throw new IllegalArgumentException("m is null");
|
|
}
|
|
if (bc == null) {
|
|
throw new IllegalArgumentException("bc is null");
|
|
}
|
|
method = m;
|
|
declaringClass = m.getDeclaringClass();
|
|
bcodes = bc;
|
|
bcLength = bc.length;
|
|
bcIndex = 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the method that this bytecode stream is from
|
|
*/
|
|
public final IMethod method() {
|
|
return method;
|
|
}
|
|
|
|
/**
|
|
* Returns the declaring class that this bytecode stream is from
|
|
*/
|
|
public final IClass declaringClass() {
|
|
return declaringClass;
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the bytecode stream Returns 0 if the method doesn't have any bytecodes (i.e. is abstract or native)
|
|
*/
|
|
public final int length() {
|
|
return bcLength;
|
|
}
|
|
|
|
/**
|
|
* Returns the current bytecode index
|
|
*/
|
|
public final int index() {
|
|
return bcIndex;
|
|
}
|
|
|
|
/**
|
|
* Resets the stream to the beginning
|
|
*/
|
|
public final void reset() {
|
|
reset(0);
|
|
}
|
|
|
|
/**
|
|
* Resets the stream to a given position Use with caution
|
|
*
|
|
* @param index the position to reset the stream to
|
|
*/
|
|
public final void reset(int index) {
|
|
bcIndex = index;
|
|
}
|
|
|
|
/**
|
|
* Does the stream have more bytecodes in it?
|
|
*/
|
|
public final boolean hasMoreBytecodes() {
|
|
return bcIndex < bcLength;
|
|
}
|
|
|
|
/**
|
|
* Returns the opcode of the next instruction in the sequence without advancing to it
|
|
*
|
|
* @return the opcode of the next instruction
|
|
* @see #nextInstruction()
|
|
*/
|
|
public final int peekNextOpcode() {
|
|
return getUnsignedByte(bcIndex);
|
|
}
|
|
|
|
/**
|
|
* Sets up the next instruction in the sequence
|
|
*
|
|
* @return the opcode of the next instruction
|
|
* @see #peekNextOpcode()
|
|
*/
|
|
public final int nextInstruction() {
|
|
opcode = readUnsignedByte();
|
|
wide = (opcode == JBC_wide);
|
|
return opcode;
|
|
}
|
|
|
|
/**
|
|
* Returns the opcode of the current instruction in the sequence Note: if skipInstruction has been called, but nextInstruction has
|
|
* not, this method will return the opcode of the skipped instruction!
|
|
*
|
|
* @return the opcode of the current instruction
|
|
* @see #nextInstruction()
|
|
* @see #isWide()
|
|
*/
|
|
public final int getOpcode() {
|
|
return opcode;
|
|
}
|
|
|
|
/**
|
|
* Are we currently processing a wide instruction?
|
|
*
|
|
* @return true if current instruction is wide
|
|
* @see #nextInstruction()
|
|
* @see #getOpcode()
|
|
*/
|
|
public final boolean isWide() {
|
|
return wide;
|
|
}
|
|
|
|
/**
|
|
* Skips the current instruction
|
|
*
|
|
* @see #skipInstruction(int,boolean)
|
|
*/
|
|
public final void skipInstruction() {
|
|
int len = JBC_length[opcode] - 1;
|
|
if (wide)
|
|
len += len;
|
|
if (len >= 0) {
|
|
bcIndex += len;
|
|
} else {
|
|
skipSpecialInstruction(opcode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Skips the current instruction (without using the opcode field) A slightly optimized version of skipInstruction()
|
|
*
|
|
* @param opc current opcode
|
|
* @param w whether current instruction follows wide
|
|
* @see #skipInstruction()
|
|
*/
|
|
public final void skipInstruction(int opc, boolean w) {
|
|
int len = JBC_length[opc] - 1;
|
|
if (w)
|
|
len += len;
|
|
if (len >= 0)
|
|
bcIndex += len;
|
|
else
|
|
skipSpecialInstruction(opc);
|
|
}
|
|
|
|
/**
|
|
* Returns a signed byte value Used for bipush
|
|
*
|
|
* @return signed byte value
|
|
*/
|
|
public final int getByteValue() {
|
|
return readSignedByte();
|
|
}
|
|
|
|
/**
|
|
* Returns a signed short value Used for sipush
|
|
*
|
|
* @return signed short value
|
|
*/
|
|
public final int getShortValue() {
|
|
return readSignedShort();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of the local (as an unsigned byte) Used for iload, lload, fload, dload, aload, istore, lstore, fstore,
|
|
* dstore, astore, iinc, ret
|
|
*
|
|
* @return local number
|
|
* @see #getWideLocalNumber()
|
|
*/
|
|
public final int getLocalNumber() {
|
|
return readUnsignedByte();
|
|
}
|
|
|
|
/**
|
|
* Returns the wide number of the local (as an unsigned short) Used for iload, lload, fload, dload, aload, istore, lstore, fstore,
|
|
* dstore, astore, iinc prefixed by wide
|
|
*
|
|
* @return wide local number
|
|
* @see #getLocalNumber()
|
|
*/
|
|
public final int getWideLocalNumber() {
|
|
return readUnsignedShort();
|
|
}
|
|
|
|
/**
|
|
* Returns an increment value (as a signed byte) Used for iinc
|
|
*
|
|
* @return increment
|
|
* @see #getWideIncrement()
|
|
*/
|
|
public final int getIncrement() {
|
|
return readSignedByte();
|
|
}
|
|
|
|
/**
|
|
* Returns an increment value (as a signed short) Used for iinc prefixed by wide
|
|
*
|
|
* @return wide increment
|
|
* @see #getIncrement()
|
|
*/
|
|
public final int getWideIncrement() {
|
|
return readSignedShort();
|
|
}
|
|
|
|
/**
|
|
* Returns the offset of the branch (as a signed short) Used for if<cond>, ificmp<cond>, ifacmp<cond>, goto, jsr
|
|
*
|
|
* @return branch offset
|
|
* @see #getWideBranchOffset()
|
|
*/
|
|
public final int getBranchOffset() {
|
|
return readSignedShort();
|
|
}
|
|
|
|
/**
|
|
* Returns the wide offset of the branch (as a signed int) Used for goto_w, jsr_w
|
|
*
|
|
* @return wide branch offset
|
|
* @see #getBranchOffset()
|
|
*/
|
|
public final int getWideBranchOffset() {
|
|
return readSignedInt();
|
|
}
|
|
|
|
/**
|
|
* Skips the padding of a switch instruction Used for tableswitch, lookupswitch
|
|
*/
|
|
public final void alignSwitch() {
|
|
int align = bcIndex & 3;
|
|
if (align != 0)
|
|
bcIndex += 4 - align; // eat padding
|
|
}
|
|
|
|
/**
|
|
* Returns the default offset of the switch (as a signed int) Used for tableswitch, lookupswitch
|
|
*
|
|
* @return default switch offset
|
|
*/
|
|
public final int getDefaultSwitchOffset() {
|
|
return readSignedInt();
|
|
}
|
|
|
|
/**
|
|
* Returns the lowest value of the tableswitch (as a signed int) Used for tableswitch
|
|
*
|
|
* @return lowest switch value
|
|
* @see #getHighSwitchValue()
|
|
*/
|
|
public final int getLowSwitchValue() {
|
|
return readSignedInt();
|
|
}
|
|
|
|
/**
|
|
* Returns the highest value of the tableswitch (as a signed int) Used for tableswitch
|
|
*
|
|
* @return highest switch value
|
|
* @see #getLowSwitchValue()
|
|
*/
|
|
public final int getHighSwitchValue() {
|
|
return readSignedInt();
|
|
}
|
|
|
|
/**
|
|
* Skips the offsets of a tableswitch instruction Used for tableswitch
|
|
*
|
|
* @param num the number of offsets to skip
|
|
* @see #getTableSwitchOffset(int)
|
|
*/
|
|
public final void skipTableSwitchOffsets(int num) {
|
|
bcIndex += (num << 2);
|
|
}
|
|
|
|
/**
|
|
* Returns the numbered offset of the tableswitch (as a signed int) Used for tableswitch The "cursor" has to be positioned at the
|
|
* start of the offset table NOTE: Will NOT advance cursor
|
|
*
|
|
* @param num the number of the offset to retrieve
|
|
* @return switch offset
|
|
*/
|
|
public final int getTableSwitchOffset(int num) {
|
|
return getSignedInt(bcIndex + (num << 2));
|
|
}
|
|
|
|
/**
|
|
* Returns the offset for a given value of the tableswitch (as a signed int) or 0 if the value is out of range. Used for
|
|
* tableswitch The "cursor" has to be positioned at the start of the offset table NOTE: Will NOT advance cursor
|
|
*
|
|
* @param value the value to retrieve offset for
|
|
* @param low the lowest value of the tableswitch
|
|
* @param high the highest value of the tableswitch
|
|
* @return switch offset
|
|
*/
|
|
public final int computeTableSwitchOffset(int value, int low, int high) {
|
|
if (value < low || value > high)
|
|
return 0;
|
|
return getSignedInt(bcIndex + ((value - low) << 2));
|
|
}
|
|
|
|
/**
|
|
* Returns the number of match-offset pairs in the lookupswitch (as a signed int) Used for lookupswitch
|
|
*
|
|
* @return number of switch pairs
|
|
*/
|
|
public final int getSwitchLength() {
|
|
return readSignedInt();
|
|
}
|
|
|
|
/**
|
|
* Skips the match-offset pairs of a lookupswitch instruction Used for lookupswitch
|
|
*
|
|
* @param num the number of match-offset pairs to skip
|
|
* @see #getLookupSwitchValue(int)
|
|
* @see #getLookupSwitchOffset(int)
|
|
*/
|
|
public final void skipLookupSwitchPairs(int num) {
|
|
bcIndex += (num << 3);
|
|
}
|
|
|
|
/**
|
|
* Returns the numbered offset of the lookupswitch (as a signed int) Used for lookupswitch The "cursor" has to be positioned at
|
|
* the start of the pair table NOTE: Will NOT advance cursor
|
|
*
|
|
* @param num the number of the offset to retrieve
|
|
* @return switch offset
|
|
* @see #getLookupSwitchValue(int)
|
|
*/
|
|
public final int getLookupSwitchOffset(int num) {
|
|
return getSignedInt(bcIndex + (num << 3) + 4);
|
|
}
|
|
|
|
/**
|
|
* Returns the numbered value of the lookupswitch (as a signed int) Used for lookupswitch The "cursor" has to be positioned at the
|
|
* start of the pair table NOTE: Will NOT advance cursor
|
|
*
|
|
* @param num the number of the value to retrieve
|
|
* @return switch value
|
|
* @see #getLookupSwitchOffset(int)
|
|
*/
|
|
public final int getLookupSwitchValue(int num) {
|
|
return getSignedInt(bcIndex + (num << 3));
|
|
}
|
|
|
|
/**
|
|
* Returns the offset for a given value of the lookupswitch (as a signed int) or 0 if the value is not in the table. Used for
|
|
* lookupswitch The "cursor" has to be positioned at the start of the offset table NOTE: Will NOT advance cursor WARNING: Uses
|
|
* LINEAR search. Whoever has time on their hands can re-implement this as a binary search.
|
|
*
|
|
* @param value the value to retrieve offset for
|
|
* @param num the number of match-offset pairs in the lookupswitch
|
|
* @return switch offset
|
|
*/
|
|
public final int computeLookupSwitchOffset(int value, int num) {
|
|
for (int i = 0; i < num; i++)
|
|
if (getSignedInt(bcIndex + (i << 3)) == value)
|
|
return getSignedInt(bcIndex + (i << 3) + 4);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Skips the extra stuff after an invokeinterface instruction Used for invokeinterface
|
|
*/
|
|
public final void alignInvokeInterface() {
|
|
bcIndex += 2; // eat superfluous stuff
|
|
}
|
|
|
|
/**
|
|
* Returns the element type (primitive) of the array (as an unsigned byte) Used for newarray
|
|
*
|
|
* @return array element type
|
|
*/
|
|
public final int getArrayElementType() {
|
|
return readUnsignedByte();
|
|
}
|
|
|
|
/**
|
|
* Returns the dimension of the array (as an unsigned byte) Used for multianewarray
|
|
*
|
|
* @return array dimension
|
|
*/
|
|
public final int getArrayDimension() {
|
|
return readUnsignedByte();
|
|
}
|
|
|
|
/**
|
|
* Returns the opcode of the wide instruction Used for wide Can be one of iload, lload, fload, dload, aload, istore, lstore,
|
|
* fstore, dstore, astore, iinc
|
|
*
|
|
* @return the opcode of the wide instruction
|
|
*/
|
|
public final int getWideOpcode() {
|
|
opcode = readUnsignedByte();
|
|
return opcode;
|
|
}
|
|
|
|
/**
|
|
* Returns the constant pool index of a constant (as an unsigned byte) Used for ldc
|
|
*
|
|
* @return constant index
|
|
* @see #getWideConstantIndex()
|
|
*/
|
|
public final int getConstantIndex() {
|
|
return readUnsignedByte();
|
|
}
|
|
|
|
/**
|
|
* Returns the wide constant pool index of a constant (as an unsigned short) Used for ldc_w, ldc2_w
|
|
*
|
|
* @return wide constant index
|
|
* @see #getConstantIndex()
|
|
*/
|
|
public final int getWideConstantIndex() {
|
|
return readUnsignedShort();
|
|
}
|
|
|
|
// // HELPER FUNCTIONS
|
|
|
|
// Skip a tableswitch or a lookupswitch instruction
|
|
private void skipSpecialInstruction(int opcode) {
|
|
switch (opcode) {
|
|
case JBC_tableswitch: {
|
|
alignSwitch();
|
|
getDefaultSwitchOffset();
|
|
int l = getLowSwitchValue();
|
|
int h = getHighSwitchValue();
|
|
skipTableSwitchOffsets(h - l + 1); // jump offsets
|
|
}
|
|
break;
|
|
case JBC_lookupswitch: {
|
|
alignSwitch();
|
|
getDefaultSwitchOffset();
|
|
int n = getSwitchLength();
|
|
skipLookupSwitchPairs(n); // match-offset pairs
|
|
}
|
|
break;
|
|
case JBC_wide: {
|
|
int oc = getWideOpcode();
|
|
int len = JBC_length[oc] - 1;
|
|
bcIndex += len + len;
|
|
}
|
|
break;
|
|
default:
|
|
throw new NullPointerException();
|
|
}
|
|
}
|
|
|
|
// // READ BYTECODES
|
|
private final byte readSignedByte() {
|
|
return bcodes[bcIndex++];
|
|
}
|
|
|
|
private final int readUnsignedByte() {
|
|
return (bcodes[bcIndex++] & 0xFF);
|
|
}
|
|
|
|
private final int getUnsignedByte(int index) {
|
|
return (bcodes[index] & 0xFF);
|
|
}
|
|
|
|
private final int readSignedShort() {
|
|
int i = bcodes[bcIndex++] << 8;
|
|
i |= (bcodes[bcIndex++] & 0xFF);
|
|
return i;
|
|
}
|
|
|
|
private final int readUnsignedShort() {
|
|
int i = (bcodes[bcIndex++] & 0xFF) << 8;
|
|
i |= (bcodes[bcIndex++] & 0xFF);
|
|
return i;
|
|
}
|
|
|
|
private final int readSignedInt() {
|
|
int i = bcodes[bcIndex++] << 24;
|
|
i |= (bcodes[bcIndex++] & 0xFF) << 16;
|
|
i |= (bcodes[bcIndex++] & 0xFF) << 8;
|
|
i |= (bcodes[bcIndex++] & 0xFF);
|
|
return i;
|
|
}
|
|
|
|
private final int getSignedInt(int index) {
|
|
int i = bcodes[index++] << 24;
|
|
i |= (bcodes[index++] & 0xFF) << 16;
|
|
i |= (bcodes[index++] & 0xFF) << 8;
|
|
i |= (bcodes[index] & 0xFF);
|
|
return i;
|
|
}
|
|
}
|