/******************************************************************************* * 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 x10.sncode; /** * A ConstantPoolParser provides read-only access to the constant pool of a * class file. */ public final class ConstantPoolParser implements SnConstants { ByteBuffer bytes; private int[] cpOffsets; private Object[] cpItems; // TODO: use JVM spec limit here? private final static int MAX_CP_ITEMS = Integer.MAX_VALUE / 4; /** * @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(ByteBuffer bytes, int itemCount) throws InvalidClassFileException { this.bytes = bytes; if (itemCount < 0 || itemCount > MAX_CP_ITEMS) { throw new IllegalArgumentException("invalid itemCount: " + itemCount); } parseConstantPool(itemCount); } /** * @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.getBytes().length < offset + required) { throw new InvalidClassFileException(offset, "file truncated, expected " + required + " bytes, saw only " + (bytes.getBytes().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 InvalidClassFileException { 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 { 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"); } Object s = cpItems[i]; if (s == null) { try { s = getCPUtf8(getInt(offset + 1)); cpItems[i] = s; } catch (IllegalArgumentException ex) { throw new InvalidClassFileException(offset, "Invalid class name at constant pool item #" + i + ": " + ex.getMessage()); } } return (String) s; } /** * @return the name of the class part of the FieldRef, MethodRef, or * InterfaceMethodRef at constant pool item i */ public Type getCPType(int i) throws InvalidClassFileException { if (i < 1 || i >= cpItems.length) { throw new IllegalArgumentException("Constant pool item #" + i + " out of range"); } int offset = cpOffsets[i]; if (offset == 0) return null; byte kind = getByte(offset); if (kind != CONSTANT_Type) throw new IllegalArgumentException("Constant pool item #" + i + " is not a Type"); Object t = cpItems[i]; if (t == null) { int b = bytes.offset(); try { bytes.seek(offset+1); t = Type.readFrom(this, bytes); cpItems[i] = t; } catch (IllegalArgumentException ex) { throw new InvalidClassFileException(offset, "Invalid Type at constant pool item #" + i + ": " + ex.getMessage()); } finally { bytes.seek(b); } } return (Type) t; } static Object transform(Object[] a) { char kind = 0; for (int i = 0; i < a.length; i++) { char k = 0; if (a[i] instanceof Double) k = 'D'; if (a[i] instanceof Float) k = 'F'; if (a[i] instanceof String) k = 'S'; if (a[i] instanceof Integer) k = 'I'; if (a[i] instanceof Type) k = 'T'; if (a[i] instanceof Long) k = 'L'; if (a[i] instanceof Constraint) k = 'C'; if (kind == 0) kind = k; else if (kind != k) kind = 'O'; } switch (kind) { case 'D': { double[] b = new double[a.length]; for (int i = 0; i < a.length; i++) b[i] = (Double) a[i]; return b; } case 'F': { float[] b = new float[a.length]; for (int i = 0; i < a.length; i++) b[i] = (Float) a[i]; return b; } case 'I': { int[] b = new int[a.length]; for (int i = 0; i < a.length; i++) b[i] = (Integer) a[i]; return b; } case 'L': { long[] b = new long[a.length]; for (int i = 0; i < a.length; i++) b[i] = (Long) a[i]; return b; } case 'T': { Type[] b = new Type[a.length]; for (int i = 0; i < a.length; i++) b[i] = (Type) a[i]; return b; } case 'C': { Constraint[] b = new Constraint[a.length]; for (int i = 0; i < a.length; i++) b[i] = (Constraint) a[i]; return b; } case 'S': { String[] b = new String[a.length]; for (int i = 0; i < a.length; i++) b[i] = (String) a[i]; return b; } } return a; } public Object getCPArray(int i) throws InvalidClassFileException { 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_Array) { throw new IllegalArgumentException("Constant pool item #" + i + " is not a Array"); } Object s = cpItems[i]; if (s == null) { try { int len = getInt(offset+1); int count = len / CP_INDEX_SIZE; offset += 5; Object[] a = new Object[count]; for (int k = 0; k < count; k++) { int index = getInt(offset); offset += CP_INDEX_SIZE; if (index == 0) a[k] = null; else { int kind = getByte(cpOffsets[index]); switch (kind) { case CONSTANT_Array: a[k] = getCPArray(index); break; case CONSTANT_Constraint: a[k] = getCPConstraint(index); break; case CONSTANT_Double: a[k] = getCPDouble(index); break; case CONSTANT_Float: a[k] = getCPDouble(index); break; case CONSTANT_Boolean: a[k] = getCPBoolean(index); break; case CONSTANT_Integer: a[k] = getCPInt(index); break; case CONSTANT_Long: a[k] = getCPLong(index); break; case CONSTANT_String: a[k] = getCPString(index); break; case CONSTANT_Type: a[k] = getCPType(index); break; case CONSTANT_Utf8: a[k] = getCPUtf8(index); break; default: throw new InvalidClassFileException(offset, "cp entry #" + index + " is not an array element."); } } } s = transform(a); } catch (IllegalArgumentException ex) { throw new InvalidClassFileException(offset, "Invalid array 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 { 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"); } Object s = cpItems[i]; if (s == null) { try { s = getCPUtf8(getInt(offset + 1)); } catch (IllegalArgumentException ex) { throw new InvalidClassFileException(offset, "Invalid string at constant pool item #" + i + ": " + ex.getMessage()); } cpItems[i] = s; } return (String) s; } private static boolean isRef(byte b) { switch (b) { case CONSTANT_MethodRef: case CONSTANT_FieldRef: 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 { 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 { 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(getInt(offset + 5)); } 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 { 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(getInt(offset + 5)); } 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 { 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(getInt(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 { 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(getInt(offset + 5)); } 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 boolean getCPBoolean(int i) throws InvalidClassFileException { 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_Boolean) { throw new IllegalArgumentException("Constant pool item #" + i + " is not a Boolean"); } return getByte(offset + 1) != 0; } /** * @return the value of the Integer at constant pool item i */ public int getCPInt(int i) throws InvalidClassFileException { 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 InvalidClassFileException { 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 InvalidClassFileException { 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 InvalidClassFileException { 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 value of the byte array at constant pool item i */ public byte[] getCPBytes(int i) throws InvalidClassFileException { 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_ByteArray) { throw new IllegalArgumentException("Constant pool item #" + i + " is not a byte array"); } int len = getInt(offset + 1); return bytes.getBytes(offset + 1 + LENGTH_SIZE, len); } /** * @return the value of the Constraint at constant pool item i */ public Constraint getCPConstraint(int i) throws InvalidClassFileException { if (i < 1 || i >= cpItems.length) { throw new IllegalArgumentException("Constant pool item #" + i + " out of range"); } int offset = cpOffsets[i]; if (offset == 0) return null; if (getByte(offset) != CONSTANT_Constraint) { throw new IllegalArgumentException("Constant pool item #" + i + " is not a Constraint"); } Object r = cpItems[i]; if (r == null) { int len = getInt(offset + 1); r = new Constraint(bytes, offset + 1 + LENGTH_SIZE, len); cpItems[i] = r; } return (Constraint) r; } private InvalidClassFileException invalidUtf8(int item, int offset) { try { return new InvalidClassFileException(offset, "Constant pool item #" + item + " starting at " + cpOffsets[item] + ", is an invalid Java Utf8 string (byte is " + getByte(offset) + ")"); } catch (InvalidClassFileException e) { return e; } } /** * @return the value of the Utf8 string at constant pool item i */ public String getCPUtf8(int i) throws InvalidClassFileException { 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"); } Object s = cpItems[i]; if (s == null) { int count = getInt(offset + 1); int end = count + offset + 5; StringBuffer buf = new StringBuffer(count); offset += 5; 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 (String) s; } private void parseConstantPool(int itemCount) throws InvalidClassFileException { int len; ByteBuffer buf = this.bytes; cpOffsets = new int[itemCount + 1]; cpItems = new Object[itemCount]; for (int i = 1; i < itemCount; i++) { int magic = bytes.getInt(); assert magic == 0xdeadbeef; cpOffsets[i] = bytes.offset(); byte tag = bytes.getByte(); switch (tag) { case CONSTANT_String: case CONSTANT_Class: bytes.getCPIndex(); break; case CONSTANT_Type: bytes.skip(Type.size(this, bytes)); break; case CONSTANT_NameAndType: case CONSTANT_MethodRef: case CONSTANT_FieldRef: bytes.getCPIndex(); bytes.getCPIndex(); break; case CONSTANT_Boolean: bytes.getByte(); break; case CONSTANT_Integer: bytes.getInt(); break; case CONSTANT_Float: bytes.getFloat(); break; case CONSTANT_Long: bytes.getLong(); break; case CONSTANT_Double: bytes.getDouble(); break; case CONSTANT_Utf8: len = bytes.getLength(); bytes.skip(len); break; case CONSTANT_Constraint: len = bytes.getLength(); bytes.skip(len); break; case CONSTANT_ByteArray: len = bytes.getLength(); bytes.skip(len); break; case CONSTANT_Array: len = bytes.getLength(); bytes.skip(len); break; default: throw new InvalidClassFileException(bytes.offset(), "unknown constant pool entry type " + tag); } } cpOffsets[itemCount] = bytes.offset(); } private byte getByte(int i) throws InvalidClassFileException { return bytes.getByte(i); } private int getUShort(int i) throws InvalidClassFileException { return bytes.getUShort(i); } private short getShort(int i) throws InvalidClassFileException { return bytes.getShort(i); } private int getInt(int i) throws InvalidClassFileException { return bytes.getInt(i); } private long getLong(int i) throws InvalidClassFileException { return bytes.getLong(i); } private float getFloat(int i) throws InvalidClassFileException { return bytes.getFloat(i); } private double getDouble(int i) throws InvalidClassFileException { return bytes.getDouble(i); } }