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 org.nanovm.converter.ClassLoader; import org.nanovm.converter.ClassInfo; import java.util.*; import java.io.*; /** * 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>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>The constant pool is a 0-based array of entries. The entry at * index 0 is never used. That is, * * <pre>constPool.getEntryAtIndex(0).isUnused() == true;</pre> * * Another quirk of the constant pool is that LONG and DOUBLE * entries take up "two" indexes. That is, * * <pre>short longIndex = constPool.addEntry(new ConstPoolEntry().setLong(0)); * constPool.getEntryAtIndex(longIndex+1).isUnused() == true;</pre> * * @see ConstPoolEntry * @see Signature */ public class ConstPool { 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>(); /** * 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, new Integer(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 (ConstPoolEntry)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 = (Integer)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) { 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 = NativeMapper.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(NativeMapper.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 = NativeMapper.getFieldId( 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 = NativeMapper.getMethodId( 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() { 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(!NativeMapper.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.load(getClassName(entry)); } else System.out.println(" -> already present"); } else System.out.println(" -> native"); } } } }