package org.nanovm.converter;
//
// NanoVMTool, Converter and Upload Tool for the NanoVM
// Copyright (C) 2005 by Till Harbaum <Till@Harbaum.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Parts of this tool are based on public domain code written by Kimberley
// Burchett: http://www.kimbly.com/code/classfile/
//
import java.io.DataInput;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
/**
* This class encapsulates constant pool management. Classfiles use
* constant pools to store information such as class names, method
* names, type signatures, and literals like "Hello World" or 123. When
* the information is needed (like for a method declaration), an index
* into the constant pool is given instead of the actual information.
* This can reduce the size of a classfile dramatically if the same
* information is used frequently.
* <p/>
* <p>ClassFileReaders and ClassFileWriters usually try to convert
* constant pool indexes into meaningful data (eg ClassInfo.setName()
* takes a String instead of a constant pool index). So you probably
* won't need to use this class unless you need to work with attributes
* that the ClassFileReader/Writer doesn't understand, such as
* bytecodes.
* <p/>
* <p>The constant pool is a 0-based array of entries. The entry at
* index 0 is never used. That is,
* <p/>
* <pre>constPool.getEntryAtIndex(0).isUnused() == true;</pre>
* <p/>
* Another quirk of the constant pool is that LONG and DOUBLE
* entries take up "two" indexes. That is,
* <p/>
* <pre>short longIndex = constPool.addEntry(new ConstPoolEntry().setLong(0));
* constPool.getEntryAtIndex(longIndex+1).isUnused() == true;</pre>
*
* @see ConstPoolEntry
*/
public class ConstPool {
private final ClassLoader classLoader;
private Vector<ConstPoolEntry> cp = new Vector<ConstPoolEntry>();
// for efficiency, addEntry() caches elements in a hashtable, which is
// used by getEntryAtIndex(). Caching reduces the time required to
// read ClassFileWriter.class from ~10s to ~7.5s.
private Hashtable<ConstPoolEntry, Integer> cpHash = new Hashtable<ConstPoolEntry, Integer>();
public ConstPool(ClassLoader classLoader){
this.classLoader = classLoader;
}
/**
* Get the size of the pool.
*/
public int size() {
return cp.size();
}
/**
* Add a constant pool entry, whether or not it's already in the pool.
* If the entry is a LONG or DOUBLE, a second, unused entry is
* also added.
*
* @returns the index of the added entry.
*/
public int addEntry(ConstPoolEntry entry) {
cp.addElement(entry);
int result = size() - 1;
cpHash.put(entry, result);
// LONG and DOUBLE entries take up two entries
if (entry.typecode() == ConstPoolEntry.LONG
|| entry.typecode() == ConstPoolEntry.DOUBLE)
cp.addElement(new ConstPoolEntry().setUnused());
return result;
}
/**
* Get the const pool entry at a given index.
*/
public ConstPoolEntry getEntryAtIndex(int index) {
return cp.elementAt(index);
}
/**
* Return the index of a constant pool entry equal to the specified one.
* If there is no matching entry in the pool yet, -1 is returned. Comparisons
* are by value, not by reference.
*/
public int getIndexOfEntryNoAdd(ConstPoolEntry entry) {
Integer i = cpHash.get(entry);
if (i != null)
return i.intValue();
return -1;
}
/**
* Return the index of a constant pool entry equal to the specified one.
* If there is no matching entry in the pool yet, a clone of the specified
* entry is added. Comparisons are made by value, not by reference.
*/
public int getIndexOfEntryAdd(ConstPoolEntry entry) {
int result = getIndexOfEntryNoAdd(entry);
if (result < 0)
result = addEntry((ConstPoolEntry) entry.clone());
return result;
}
// used for doing searches -- reuse so we don't have to allocate a new
// one every time.
private ConstPoolEntry searchEntry = new ConstPoolEntry();
/**
* Convenience method to return the index of the UTF const pool entry
* equal to the given string. If there is no matching entry in the const pool yet,
* a new one is added. Without this method, something like the following code
* would be used:
* <pre>constPool.getIndexOfEntryAdd(new ConstPoolEntry().setUTF(str));</pre>
* This method avoids the overhead of allocating a ConstPoolEntry every time.
*/
public int getIndexOfUTFAdd(String str) {
return getIndexOfEntryAdd(searchEntry.setUTF(str));
}
/**
* Convenience method to return the index of the CLASS const pool entry
* whose classname is equal to the given string. If there is no matching entry in
* the const pool yet, a new one is added. Without this method, something like
* the following code would be used:
* <pre>short nameIndex = constPool.getIndexOfUTFAdd(classname);
* constPool.getIndexEntryAdd(new ConstPoolEntry().setClass(nameIndex));</pre>
* This method avoids the overhead of allocating a ConstPoolEntry every time.
*/
public int getIndexOfClassAdd(String classname) {
searchEntry.setClass(getIndexOfUTFAdd(classname));
return getIndexOfEntryAdd(searchEntry);
}
/**
* Read in the constant pool from a stream.
*
* @throws ClassFileReadException if the class file is corrupt.
* @throws IOException if the DataInput throws an IOException.
*/
public void read(DataInput in) throws IOException {
int count = in.readShort();
if (Debug.strConstPool != null)
Debug.println(Debug.strConstPool,
"# of entries = " + count);
// entry 0 is unused
addEntry(new ConstPoolEntry().setUnused());
// note: start i at 1 because 0 is unused
for (int i = 1; i < count; i++) {
ConstPoolEntry entry = new ConstPoolEntry();
entry.read(in, entry);
if (Debug.strConstPool != null)
Debug.println(Debug.strConstPool,
i + " " + entry.toString());
addEntry(entry);
// LONG and DOUBLE entries take up two slots
if (ConstPoolEntry.LONG == entry.typecode()
|| ConstPoolEntry.DOUBLE == entry.typecode())
i++;
}
}
// return total number of constant entries
public int totalConstantEntries() {
int num = 0;
for (int i = 0; i < size(); i++) {
ConstPoolEntry entry = getEntryAtIndex(i);
// check for ints and floats only
if (entry.typecode() == ConstPoolEntry.FLOAT
|| entry.typecode() == ConstPoolEntry.INT)
num++;
}
return num;
}
// return the n'th constant entry in this constant pool
public int getConstantEntry(int n) {
for (int i = 0; i < size(); i++) {
ConstPoolEntry entry = getEntryAtIndex(i);
// check for ints and floats only
if (entry.typecode() == ConstPoolEntry.FLOAT) {
if (n-- == 0)
return encodeFloat(entry.getFloat());
}
if (entry.typecode() == ConstPoolEntry.INT) {
if (n-- == 0)
return entry.getInt();
}
}
return 0;
}
// return total number of strings
public int totalStrings() {
int num = 0;
for (int i = 0; i < size(); i++) {
ConstPoolEntry entry = getEntryAtIndex(i);
// check for strings only
if (entry.typecode() == ConstPoolEntry.STRING)
num++;
}
return num;
}
// return size of all strings stored in this constant pool
public int totalStringSize() {
int sum = 0;
for (int i = 0; i < size(); i++) {
ConstPoolEntry entry = getEntryAtIndex(i);
// check for strings only
if (entry.typecode() == ConstPoolEntry.STRING) {
// get everything we need to know about the methods
ConstPoolEntry stringRef =
getEntryAtIndex(entry.getStringIndex());
sum += stringRef.getString().length() + 1;
}
}
return sum;
}
// return the n'th string in this constant pool
public String getString(int n) {
for (int i = 0; i < size(); i++) {
ConstPoolEntry entry = getEntryAtIndex(i);
// check for strings only
if (entry.typecode() == ConstPoolEntry.STRING)
if (n-- == 0)
return getEntryAtIndex(entry.getStringIndex()).getString();
}
return null;
}
String getClassName(ConstPoolEntry entry) {
return getEntryAtIndex(getEntryAtIndex(entry.getClassIndex()).
getClassNameIndex()).getString();
}
String getMethodName(ConstPoolEntry entry) {
return getEntryAtIndex(getEntryAtIndex(entry.getNameAndTypeIndex()).
getNameIndex()).getString();
}
String getMethodType(ConstPoolEntry entry) {
return getEntryAtIndex(getEntryAtIndex(entry.getNameAndTypeIndex()).
getTypeIndex()).getString();
}
String getFieldName(ConstPoolEntry entry) {
return getEntryAtIndex(getEntryAtIndex(entry.getNameAndTypeIndex()).
getNameIndex()).getString();
}
String getFieldType(ConstPoolEntry entry) {
return getEntryAtIndex(getEntryAtIndex(entry.getNameAndTypeIndex()).
getTypeIndex()).getString();
}
// since our final file does not contain a constant pool, the various
// references into the constant pool has to ne updated into something
// that can be directly used to call methods, push string references etc.
// This is what the following code does with an index to a constant pool
// reference.
static int encodeFloat(float val) {
int ival = Float.floatToRawIntBits(val);
boolean sign = ival < 0;
int exponent = ival >> 23 & 0xff;
ival &= 0x007fffff;
if (exponent == 0xff)
exponent = 0x7f;
else if (exponent != 0x00)
exponent -= 0x40;
ival |= exponent << 23;
if (sign)
ival |= 0x40000000;
ival &= 0x7fffffff;
return ival;
}
static int encodeInt(int val) {
val = val & 0x7fffffff;
return val;
}
// referenz auf diese klasse mitgeben
public int constantRelocate(int index) throws ClassNotFoundException {
ConstPoolEntry entry = getEntryAtIndex(index);
int id = 0xffff;
System.out.print(" -> ");
if (entry.typecode() == ConstPoolEntry.CLASS) {
String className = getEntryAtIndex(
entry.getClassNameIndex()).getString();
System.out.print("class, " + className);
// check if there's a local class with this name
id = classLoader.getClassIndex(className);
if (id >= 0)
System.out.println("local id: #" + Integer.toHexString(id << 8));
else {
id = classLoader.getNativeClassId(className);
if (id >= 0)
System.out.println("native id: #" + Integer.toHexString(id << 8));
else {
System.out.println("Unable to map class reference");
System.exit(-1);
}
}
id <<= 8; // class id
} else if (entry.typecode() == ConstPoolEntry.FIELDREF) {
System.out.print("field " + getClassName(entry) + ":" +
getFieldName(entry) + "." + getFieldType(entry) + ", ");
// check if this is a local field
// try to get the local method index
if (classLoader.fieldExists(
getClassName(entry), getMethodName(entry), getMethodType(entry))) {
System.out.print("local, ");
// check if static or not
FieldInfo fieldInfo = classLoader.getFieldInfo(
getClassName(entry), getMethodName(entry), getMethodType(entry));
if ((fieldInfo.getAccessFlags() & AccessFlags.STATIC) != 0) {
// index is the number of field in this whole set of classes
id = classLoader.getStaticFieldIndex(getClassName(entry),
getMethodName(entry), getMethodType(entry));
System.out.println("static id: #" + Integer.toHexString(id));
} else {
// non-static, get access to class it resides in
ClassInfo classInfo = classLoader.getClassInfo(getClassName(entry));
// index is the number of non static fields in this local class
id = classInfo.getFieldIndex(0, getMethodName(entry), getMethodType(entry));
// plus the total number of fields in the super classes
// walk through all super classes
String className = getClassName(entry);
while (classLoader.getNativeClassId(classLoader.
getSuperClassName(className)) == -1) {
className = classLoader.getSuperClassName(className);
id += classLoader.getClassInfo(className).nonStaticFields();
}
System.out.println("non static id: #" + Integer.toHexString(id));
}
} else {
// we only support static native fields
id = classLoader.getNativeFieldId(
getClassName(entry), getMethodName(entry), getMethodType(entry));
if (id == -1) {
System.out.println("Unable to map field reference");
System.out.println("class=" + getClassName(entry) + " method=" + getMethodName(entry) + " type=" + getMethodType(entry));
System.exit(-1);
}
System.out.println("native, id: #" + Integer.toHexString(id));
}
} else if (entry.typecode() == ConstPoolEntry.STRING) {
String str = getEntryAtIndex(entry.getStringIndex()).getString();
System.out.print("string \"" + str + "\", ");
// search for this string
for (int i = 0; i < classLoader.totalStrings(); i++)
if (classLoader.getString(i).equals(str))
id = i + classLoader.totalConstantEntries();
if (id == 0xffff) {
System.out.println("Unable to map string reference");
System.exit(-1);
} else
System.out.println("id: #" + Integer.toHexString(id));
} else if (entry.typecode() == ConstPoolEntry.METHODREF) {
System.out.print("method " + getClassName(entry) + ":" +
getMethodName(entry) + "." + getMethodType(entry) + ", ");
// try to get the local method index
index = classLoader.getMethodIndex(
getClassName(entry), getMethodName(entry), getMethodType(entry));
// didn't work? try native methods
if (index == -1) {
id = classLoader.getNativeMethodId(
getClassName(entry), getMethodName(entry), getMethodType(entry));
if (id == -1) {
System.out.println("Unable to map method reference");
System.exit(-1);
}
System.out.println("native id: #" + Integer.toHexString(id));
} else {
// new id is just the index of the method in the file
id = index;
System.out.println("local id: #" + Integer.toHexString(id));
}
} else if (entry.typecode() == ConstPoolEntry.FLOAT) {
float val = entry.getFloat();
int ival = encodeFloat(val);
System.out.print("float :" + val + " = 0x" + Integer.toHexString(ival) + ", ");
// search for this constant
for (int i = 0; i < classLoader.totalConstantEntries(); i++)
if (classLoader.getConstantEntry(i) == ival)
id = i;
if (id == 0xffff) {
System.out.println("Unable to map constant");
System.exit(-1);
} else
System.out.println("id: #" + Integer.toHexString(id));
} else if (entry.typecode() == ConstPoolEntry.INT) {
int val = entry.getInt();
int ival = encodeInt(val);
System.out.print("integer :" + val + " = 0x" + Integer.toHexString(ival) + ", ");
// search for this constant
for (int i = 0; i < classLoader.totalConstantEntries(); i++)
if (classLoader.getConstantEntry(i) == ival)
id = i;
if (id == 0xffff) {
System.out.println("Unable to map constant");
System.exit(-1);
} else
System.out.println("id: #" + Integer.toHexString(id));
} else {
System.out.println("ERROR: Not a relocatable type");
System.exit(-1);
}
return id;
}
public void resolveMethodRefs() throws ClassNotFoundException {
System.out.println("Resolving method references ...");
for (int i = 0; i < size(); i++) {
ConstPoolEntry entry = getEntryAtIndex(i);
// check for method references only
if (entry.typecode() == ConstPoolEntry.METHODREF) {
// get everything we need to know about the methods
System.out.print("Method " + getClassName(entry) + "." +
getMethodName(entry) + ":" + getMethodType(entry));
if (!classLoader.methodIsNative(getClassName(entry),
getMethodName(entry), getMethodType(entry))) {
// check if we already know a method like this
if (!classLoader.methodExists(getClassName(entry),
getMethodName(entry), getMethodType(entry))) {
System.out.println(" -> missing");
// try to load appropriate class
classLoader.getClassInfo(getClassName(entry));
} else
System.out.println(" -> already present");
} else
System.out.println(" -> native");
}
}
}
}