WALA/com.ibm.wala.shrike/src/com/ibm/wala/shrikeCT/ConstantPoolParser.java

679 lines
24 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.shrikeCT;
import com.ibm.wala.shrikeCT.BootstrapMethodsReader.BootstrapMethod;
import com.ibm.wala.shrikeCT.ClassReader.AttrIterator;
/**
* A ConstantPoolParser provides read-only access to the constant pool of a class file.
*/
public final class ConstantPoolParser implements ClassConstants {
public static class ReferenceToken {
private final byte kind;
private final String className;
private final String elementName;
private final String descriptor;
public ReferenceToken(byte kind, String className, String elementName, String descriptor) {
this.kind = kind;
this.className = className;
this.elementName = elementName;
this.descriptor = descriptor;
}
public byte getKind() {
return kind;
}
public String getClassName() {
return className;
}
public String getElementName() {
return elementName;
}
public String getDescriptor() {
return descriptor;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((className == null) ? 0 : className.hashCode());
result = prime * result + ((descriptor == null) ? 0 : descriptor.hashCode());
result = prime * result + ((elementName == null) ? 0 : elementName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReferenceToken other = (ReferenceToken) obj;
if (kind != other.kind) {
return false;
}
if (className == null) {
if (other.className != null)
return false;
} else if (!className.equals(other.className))
return false;
if (descriptor == null) {
if (other.descriptor != null)
return false;
} else if (!descriptor.equals(other.descriptor))
return false;
if (elementName == null) {
if (other.elementName != null)
return false;
} else if (!elementName.equals(other.elementName))
return false;
return true;
}
}
final private byte[] bytes;
private int[] cpOffsets;
private String[] cpItems;
private BootstrapMethodsReader invokeDynamicBootstraps;
// TODO: use JVM spec limit here?
private final static int MAX_CP_ITEMS = Integer.MAX_VALUE / 4;
private BootstrapMethodsReader getBootstrapReader() throws InvalidClassFileException {
if (invokeDynamicBootstraps == null) {
ClassReader thisClass = new ClassReader(bytes);
AttrIterator attrs = new AttrIterator();
thisClass.initClassAttributeIterator(attrs);
for (; attrs.isValid(); attrs.advance()) {
if (attrs.getName().equals("BootstrapMethods")) {
invokeDynamicBootstraps = new BootstrapMethodsReader(attrs);
break;
}
}
assert invokeDynamicBootstraps != null;
}
return invokeDynamicBootstraps;
}
/**
* @param bytes the raw class file data
* @param offset the start of the constant pool data
* @param itemCount the number of items in the pool
*/
public ConstantPoolParser(byte[] bytes, int offset, int itemCount) throws InvalidClassFileException {
this.bytes = bytes;
if (offset < 0) {
throw new IllegalArgumentException("invalid offset: " + offset);
}
if (itemCount < 0 || itemCount > MAX_CP_ITEMS) {
throw new IllegalArgumentException("invalid itemCount: " + itemCount);
}
parseConstantPool(offset, itemCount);
}
/**
* @return the buffer holding the raw class file data
*/
public byte[] getRawBytes() {
return bytes;
}
/**
* @return the offset of the constant pool data in the raw class file buffer
*/
public int getRawOffset() throws IllegalStateException {
if (cpOffsets.length < 2) {
throw new IllegalStateException();
}
return cpOffsets[1];
}
/**
* @return the size of the constant pool data in the raw class file buffer
*/
public int getRawSize() throws IllegalStateException {
if (cpOffsets.length < 2) {
throw new IllegalStateException();
}
return cpOffsets[cpOffsets.length - 1] - cpOffsets[1];
}
/**
* @return the number of constant pool items (maximum item index plus one)
*/
public int getItemCount() {
return cpOffsets.length - 1;
}
private void checkLength(int offset, int required) throws InvalidClassFileException {
if (bytes.length < offset + required) {
throw new InvalidClassFileException(offset, "file truncated, expected " + required + " bytes, saw only "
+ (bytes.length - offset));
}
}
/**
* @return the type of constant pool item i, or 0 if i is an unused constant pool item
*/
public byte getItemType(int i) throws IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0) {
return 0;
} else {
return getByte(offset);
}
}
/**
* @return the name of the Class at constant pool item i, in JVM format (e.g., java/lang/Object)
*/
public String getCPClass(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_Class) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Class");
}
String s = cpItems[i];
if (s == null) {
try {
s = getCPUtf8(getUShort(offset + 1));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid class name at constant pool item #" + i + ": " + ex.getMessage());
}
cpItems[i] = s;
}
return s;
}
/**
* @return the name of the method at constant pool item i, in JVM format (e.g., java/lang/Object)
*/
public String getCPMethodType(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_MethodType) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a MethodType");
}
String s = cpItems[i];
if (s == null) {
try {
s = getCPUtf8(getUShort(offset + 1));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid method type at constant pool item #" + i + ": " + ex.getMessage());
}
cpItems[i] = s;
}
return s;
}
/**
* @return the String at constant pool item i
*/
public String getCPString(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_String) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a String");
}
String s = cpItems[i];
if (s == null) {
try {
s = getCPUtf8(getUShort(offset + 1));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid string at constant pool item #" + i + ": " + ex.getMessage());
}
cpItems[i] = s;
}
return s;
}
/**
* Does b represent the tag of a constant pool reference to an (interface)
* method or field?
*/
public static boolean isRef(byte b) {
switch (b) {
case CONSTANT_MethodRef:
case CONSTANT_FieldRef:
case CONSTANT_InterfaceMethodRef:
return true;
default:
return false;
}
}
/**
* @return the name of the class part of the FieldRef, MethodRef, or InterfaceMethodRef at constant pool item i
*/
public String getCPRefClass(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || !isRef(getByte(offset))) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Ref");
}
try {
return getCPClass(getUShort(offset + 1));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid Ref class at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the name part of the FieldRef, MethodRef, or InterfaceMethodRef at constant pool item i
*/
public String getCPRefName(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || !isRef(getByte(offset))) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Ref");
}
try {
return getCPNATName(getUShort(offset + 3));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid Ref NameAndType at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the type part of the FieldRef, MethodRef, or InterfaceMethodRef at constant pool item i, in JVM format (e.g., I, Z, or
* Ljava/lang/Object;)
*/
public String getCPRefType(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || !isRef(getByte(offset))) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Ref");
}
try {
return getCPNATType(getUShort(offset + 3));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid Ref NameAndType at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the name part of the NameAndType at constant pool item i
*/
public String getCPNATName(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_NameAndType) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a NameAndType");
}
try {
return getCPUtf8(getUShort(offset + 1));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid NameAndType name at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the type part of the NameAndType at constant pool item i, in JVM format (e.g., I, Z, or Ljava/lang/Object;)
*/
public String getCPNATType(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_NameAndType) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a NameAndType");
}
try {
return getCPUtf8(getUShort(offset + 3));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid NameAndType type at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the name part of the MethodHandle at constant pool item i, in JVM format (e.g., I, Z, or Ljava/lang/Object;)
*/
public String getCPHandleName(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_MethodHandle) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a MethodHandle");
}
try {
return getCPRefName(getUShort(offset + 2));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid NameAndType type at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the name part of the MethodHandle at constant pool item i, in JVM format (e.g., I, Z, or Ljava/lang/Object;)
*/
public String getCPHandleType(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_MethodHandle) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a MethodHandle");
}
try {
return getCPRefType(getUShort(offset + 2));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid NameAndType type at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the name part of the MethodHandle at constant pool item i, in JVM format (e.g., I, Z, or Ljava/lang/Object;)
*/
public String getCPHandleClass(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_MethodHandle) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a MethodHandle");
}
try {
return getCPRefClass(getUShort(offset + 2));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid NameAndType type at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the type of the MethodHandle at constant pool item i
*/
public byte getCPHandleKind(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_MethodHandle) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a MethodHandle");
}
try {
return getByte(offset + 1);
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid NameAndType type at constant pool item #" + i + ": " + ex.getMessage());
}
}
/**
* @return the value of the Integer at constant pool item i
*/
public int getCPInt(int i) throws IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_Integer) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not an Integer");
}
return getInt(offset + 1);
}
/**
* @return the value of the Float at constant pool item i
*/
public float getCPFloat(int i) throws IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_Float) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Float");
}
return getFloat(offset + 1);
}
/**
* @return the value of the Long at constant pool item i
*/
public long getCPLong(int i) throws IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_Long) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Long");
}
return getLong(offset + 1);
}
/**
* @return the value of the Double at constant pool item i
*/
public double getCPDouble(int i) throws IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_Double) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Double");
}
return getDouble(offset + 1);
}
/**
* @return the BootstrapMethodTable index of the bootstrap method for this invokedynamic
*/
public BootstrapMethod getCPDynBootstrap(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_InvokeDynamic) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not an InvokeDynamic");
}
try {
int index = getUShort(offset + 1);
return getBootstrapReader().getEntry(index);
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid Ref class at constant pool item #" + i + ": " + ex.getMessage());
}
}
public String getCPDynName(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_InvokeDynamic) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not an InvokeDynamic");
}
try {
return getCPNATName(getUShort(offset + 3));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid Ref class at constant pool item #" + i + ": " + ex.getMessage());
}
}
public String getCPDynType(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_InvokeDynamic) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not an InvokeDynamic");
}
try {
return getCPNATType(getUShort(offset + 3));
} catch (IllegalArgumentException ex) {
throw new InvalidClassFileException(offset, "Invalid Ref class at constant pool item #" + i + ": " + ex.getMessage());
}
}
private InvalidClassFileException invalidUtf8(int item, int offset) {
return new InvalidClassFileException(offset, "Constant pool item #" + item + " starting at " + cpOffsets[item]
+ ", is an invalid Java Utf8 string (byte is " + getByte(offset) + ")");
}
/**
* @return the value of the Utf8 string at constant pool item i
*/
public String getCPUtf8(int i) throws InvalidClassFileException, IllegalArgumentException {
if (i < 1 || i >= cpItems.length) {
throw new IllegalArgumentException("Constant pool item #" + i + " out of range");
}
int offset = cpOffsets[i];
if (offset == 0 || getByte(offset) != CONSTANT_Utf8) {
throw new IllegalArgumentException("Constant pool item #" + i + " is not a Utf8");
}
String s = cpItems[i];
if (s == null) {
int count = getUShort(offset + 1);
int end = count + offset + 3;
StringBuffer buf = new StringBuffer(count);
offset += 3;
while (offset < end) {
byte x = getByte(offset);
if ((x & 0x80) == 0) {
if (x == 0) {
throw invalidUtf8(i, offset);
}
buf.append((char) x);
offset++;
} else if ((x & 0xE0) == 0xC0) {
if (offset + 1 >= end) {
throw invalidUtf8(i, offset);
}
byte y = getByte(offset + 1);
if ((y & 0xC0) != 0x80) {
throw invalidUtf8(i, offset);
}
buf.append((char) (((x & 0x1F) << 6) + (y & 0x3F)));
offset += 2;
} else if ((x & 0xF0) == 0xE0) {
if (offset + 2 >= end) {
throw invalidUtf8(i, offset);
}
byte y = getByte(offset + 1);
byte z = getByte(offset + 2);
if ((y & 0xC0) != 0x80 || (z & 0xC0) != 0x80) {
throw invalidUtf8(i, offset);
}
buf.append((char) (((x & 0x0F) << 12) + ((y & 0x3F) << 6) + (z & 0x3F)));
offset += 3;
} else {
throw invalidUtf8(i, offset);
}
}
// s = buf.toString().intern(); // removed intern() call --MS
s = buf.toString();
cpItems[i] = s;
}
return s;
}
private void parseConstantPool(int offset, int itemCount) throws InvalidClassFileException {
cpOffsets = new int[itemCount + 1];
cpItems = new String[itemCount];
for (int i = 1; i < itemCount; i++) {
cpOffsets[i] = offset;
byte tag = getByte(offset);
int itemLen;
switch (tag) {
case CONSTANT_String:
case CONSTANT_Class:
itemLen = 2;
break;
case CONSTANT_NameAndType:
case CONSTANT_MethodRef:
case CONSTANT_FieldRef:
case CONSTANT_InterfaceMethodRef:
case CONSTANT_Integer:
case CONSTANT_Float:
itemLen = 4;
break;
case CONSTANT_Long:
case CONSTANT_Double:
itemLen = 8;
i++; // ick
break;
case CONSTANT_Utf8:
itemLen = 2 + getUShort(offset + 1);
break;
case CONSTANT_MethodHandle:
itemLen = 3;
break;
case CONSTANT_MethodType:
itemLen = 2;
break;
case CONSTANT_InvokeDynamic:
itemLen = 4;
break;
default:
throw new InvalidClassFileException(offset, "unknown constant pool entry type" + tag);
}
checkLength(offset, itemLen);
offset += itemLen + 1;
}
cpOffsets[itemCount] = offset;
}
private byte getByte(int i) {
return bytes[i];
}
private int getUShort(int i) {
return ((bytes[i] & 0xFF) << 8) + (bytes[i + 1] & 0xFF);
}
// private short getShort(int i) {
// return (short) ((bytes[i] << 8) + (bytes[i + 1] & 0xFF));
// }
private int getInt(int i) {
return (bytes[i] << 24) + ((bytes[i + 1] & 0xFF) << 16) + ((bytes[i + 2] & 0xFF) << 8) + (bytes[i + 3] & 0xFF);
}
private long getLong(int i) {
return ((long) getInt(i) << 32) + (getInt(i + 4) & 0xFFFFFFFFL);
}
private float getFloat(int i) {
return Float.intBitsToFloat(getInt(i));
}
private double getDouble(int i) {
return Double.longBitsToDouble(getLong(i));
}
}