package com.macmillan.nmeyers; import java.io.*; /* * ClassFileContents: Utility class to read the contents of a class file. * * Author: Nathan Meyers, nmeyers@javalinux.net * $Id: ClassFileContents.java,v 1.3 1999/11/08 03:40:41 nathanm Exp $ * * 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, 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 * with this program. If not, the license is available from the * GNU project, at http://www.gnu.org. */ public class ClassFileContents { // Class file layout from Java spec int magic; short minor_version; short major_version; int constant_pool_count; cp_info constant_pool[]; final static byte CONSTANT_Utf8 = 1; final static byte CONSTANT_Integer = 3; final static byte CONSTANT_Float = 4; final static byte CONSTANT_Long = 5; final static byte CONSTANT_Double = 6; final static byte CONSTANT_Class = 7; final static byte CONSTANT_String = 8; final static byte CONSTANT_Fieldref = 9; final static byte CONSTANT_Methodref = 10; final static byte CONSTANT_InterfaceMethodref = 11; final static byte CONSTANT_NameAndType = 12; public class cp_info { byte tag; } // Reader for next cp_info entry cp_info read_cp_info(DataInputStream is) throws IOException { byte tag = is.readByte(); switch (tag) { case CONSTANT_Utf8: return new CONSTANT_Utf8_info(tag, is); case CONSTANT_Integer: return new CONSTANT_Integer_info(tag, is); case CONSTANT_Float: return new CONSTANT_Float_info(tag, is); case CONSTANT_Long: return new CONSTANT_Long_info(tag, is); case CONSTANT_Double: return new CONSTANT_Double_info(tag, is); case CONSTANT_Class: return new CONSTANT_Class_info(tag, is); case CONSTANT_String: return new CONSTANT_String_info(tag, is); case CONSTANT_Fieldref: return new CONSTANT_Fieldref_info(tag, is); case CONSTANT_Methodref: return new CONSTANT_Methodref_info(tag, is); case CONSTANT_InterfaceMethodref: return new CONSTANT_InterfaceMethodref_info(tag, is); case CONSTANT_NameAndType: return new CONSTANT_NameAndType_info(tag, is); } throw new IOException("Unrecognized tag " + tag); } // Subclasses of cp_info for Java types public class CONSTANT_Class_info extends cp_info { int name_index; CONSTANT_Class_info(byte t, DataInputStream is) throws IOException { tag = t; name_index = is.readUnsignedShort(); } public String toString() { return "[Class:name_index:" + nameIndex(name_index) + ']'; } } public class CONSTANT_Fieldref_info extends cp_info { int class_index; int name_and_type_index; CONSTANT_Fieldref_info(byte t, DataInputStream is) throws IOException { tag = t; class_index = is.readUnsignedShort(); name_and_type_index = is.readUnsignedShort(); } public String toString() { return "[Fieldref:class_index:" + class_index + ",name_and_type_index:" + name_and_type_index + ']'; } } public class CONSTANT_Methodref_info extends cp_info { int class_index; int name_and_type_index; CONSTANT_Methodref_info(byte t, DataInputStream is) throws IOException { tag = t; class_index = is.readUnsignedShort(); name_and_type_index = is.readUnsignedShort(); } public String toString() { return "[Methodref:class_index:" + class_index + ",name_and_type_index:" + name_and_type_index + ']'; } } public class CONSTANT_InterfaceMethodref_info extends cp_info { int class_index; int name_and_type_index; CONSTANT_InterfaceMethodref_info(byte t, DataInputStream is) throws IOException { tag = t; class_index = is.readUnsignedShort(); name_and_type_index = is.readUnsignedShort(); } public String toString() { return "[InterfaceMethodref:class_index:" + class_index + ",name_and_type_index:" + name_and_type_index + ']'; } } public class CONSTANT_String_info extends cp_info { int string_index; CONSTANT_String_info(byte t, DataInputStream is) throws IOException { tag = t; string_index = is.readUnsignedShort(); } public String toString() { return "[String:string_index:" + string_index + ']'; } } public class CONSTANT_Integer_info extends cp_info { int bytes; CONSTANT_Integer_info(byte t, DataInputStream is) throws IOException { tag = t; bytes = is.readInt(); } public String toString() { return "[Integer:bytes:" + bytes + ']'; } } public class CONSTANT_Float_info extends cp_info { float value; CONSTANT_Float_info(byte t, DataInputStream is) throws IOException { tag = t; value = is.readFloat(); } public String toString() { return "[Float:value:" + value + ']'; } } public class CONSTANT_Long_info extends cp_info { long value; CONSTANT_Long_info(byte t, DataInputStream is) throws IOException { tag = t; value = is.readLong(); } public String toString() { return "[Long:value:" + value + ']'; } } public class CONSTANT_Double_info extends cp_info { double value; CONSTANT_Double_info(byte t, DataInputStream is) throws IOException { tag = tag; value = is.readDouble(); } public String toString() { return "[Double:value:" + value + ']'; } } public class CONSTANT_NameAndType_info extends cp_info { int name_index; int descriptor_index; CONSTANT_NameAndType_info(byte t, DataInputStream is) throws IOException { tag = t; name_index = is.readUnsignedShort(); descriptor_index = is.readUnsignedShort(); } public String toString() { return "[NameAndType:name_index:" + nameIndex(name_index) + ",descriptor_index:" + descriptor_index + ']'; } } public class CONSTANT_Utf8_info extends cp_info { int length; byte bytes[]; CONSTANT_Utf8_info(byte t, DataInputStream is) throws IOException { tag = t; length = is.readUnsignedShort(); bytes = new byte[length]; for (int i = 0; i < length; i++) bytes[i] = is.readByte(); } public String getString() { String stringInfo = null; try { stringInfo = new String(bytes, "UTF8"); } catch (UnsupportedEncodingException e) {} return stringInfo; } public String toString() { return "[UTF8String:length:" + length + ",bytes:" + getString() + ']'; } } short access_flags; final short ACC_PUBLIC=0x0001; final short ACC_FINAL=0x0010; final short ACC_SUPER=0x0020; final short ACC_INTERFACE=0x0200; final short ACC_ABSTRACT=0x0400; int this_class; int super_class; int interfaces_count; int interfaces[]; int fields_count; field_info fields[]; public class field_info { short access_flags; final short ACC_PUBLIC = 0x0001; final short ACC_PRIVATE = 0x0002; final short ACC_PROTECTED = 0x0004; final short ACC_STATIC = 0x0008; final short ACC_FINAL = 0x0010; final short ACC_VOLATILE = 0x0040; final short ACC_TRANSIENT = 0x0080; int name_index; int descriptor_index; int attributes_count; attribute_info attributes[]; field_info(DataInputStream is) throws IOException { access_flags = is.readShort(); name_index = is.readUnsignedShort(); descriptor_index = is.readUnsignedShort(); attributes_count = is.readUnsignedShort(); attributes = new attribute_info[attributes_count]; for (int i = 0; i < attributes_count; i++) attributes[i] = new attribute_info(is); } public String toString() { String result = "[access_flags:" + hex(access_flags) + ",name_index:" + nameIndex(name_index) + ",descriptor_index:" + descriptor_index + ",attributes_count:" + attributes_count + ",attributes:["; for (int i = 0; i < attributes_count; i++) { if (i > 0) result += ','; result += attributes[i].toString(); } result += "]]"; return result; } } int methods_count; method_info methods[]; public class method_info { short access_flags; final short ACC_PUBLIC = 0x0001; final short ACC_PRIVATE = 0x0002; final short ACC_PROTECTED = 0x0004; final short ACC_STATIC = 0x0008; final short ACC_FINAL = 0x0010; final short ACC_SYNCHRONIZED = 0x0020; final short ACC_NATIVE = 0x0100; final short ACC_ABSTRACT = 0x0400; int name_index; int descriptor_index; int attributes_count; attribute_info attributes[]; method_info(DataInputStream is) throws IOException { access_flags = is.readShort(); name_index = is.readUnsignedShort(); descriptor_index = is.readUnsignedShort(); attributes_count = is.readUnsignedShort(); attributes = new attribute_info[attributes_count]; for (int i = 0; i < attributes_count; i++) attributes[i] = new attribute_info(is); } public String toString() { String result = "[access_flags:" + hex(access_flags) + ",name_index:" + nameIndex(name_index) + ",descriptor_index:" + descriptor_index + ",attributes_count:" + attributes_count + ",attributes:["; for (int i = 0; i < attributes_count; i++) { if (i > 0) result += ','; result += attributes[i].toString(); } result += "]]"; return result; } } int attributes_count; attribute_info attributes[]; public class attribute_info { int attribute_name_index; int attribute_length; byte info[]; attribute_info(DataInputStream is) throws IOException { attribute_name_index = is.readUnsignedShort(); attribute_length = is.readInt(); info = new byte[attribute_length]; for (int i = 0; i < attribute_length; i++) info[i] = is.readByte(); } public String toString() { String result = "[attribute_name_index:" + nameIndex(attribute_name_index) + ",attribute_length:" + attribute_length + ",info:0x"; for (int i = 0; i < attribute_length; i++) result += hex(info[i]); return result + ']'; } } static public class Exceptions_attribute { int attribute_name_index; int attribute_length; int number_of_exceptions; int exception_index_table[]; Exceptions_attribute(attribute_info info) throws IOException { attribute_name_index = info.attribute_name_index; attribute_length = info.attribute_length; DataInputStream is = new DataInputStream( new ByteArrayInputStream(info.info)); number_of_exceptions = is.readUnsignedShort(); exception_index_table = new int[number_of_exceptions]; for (int i = 0; i < number_of_exceptions; i++) exception_index_table[i] = is.readUnsignedShort(); is.close(); } } public ClassFileContents(InputStream is) throws IOException { DataInputStream inputStream = new DataInputStream(is); magic = inputStream.readInt(); if (magic != 0xcafebabe) throw new IOException("Not a class file"); minor_version = inputStream.readShort(); major_version = inputStream.readShort(); constant_pool_count = inputStream.readUnsignedShort(); constant_pool = new cp_info[constant_pool_count]; constant_pool[0] = null; for (int i = 1; i < constant_pool_count; i++) { constant_pool[i] = read_cp_info(inputStream); // Kluge around strange classfile representation of // longs and doubles in the constant pool. The spec // indicates that this representation was not, in // retrospect, a good idea. if (constant_pool[i].getClass().equals( CONSTANT_Long_info.class) || constant_pool[i].getClass().equals( CONSTANT_Double_info.class)) constant_pool[++i] = null; } access_flags = inputStream.readShort(); this_class = inputStream.readUnsignedShort(); super_class = inputStream.readUnsignedShort(); interfaces_count = inputStream.readUnsignedShort(); interfaces = new int[interfaces_count]; for (int i = 0; i < interfaces_count; i++) { interfaces[i] = inputStream.readUnsignedShort(); } fields_count = inputStream.readUnsignedShort(); fields = new field_info[fields_count]; for (int i = 0; i < fields_count; i++) { fields[i] = new field_info(inputStream); } methods_count = inputStream.readUnsignedShort(); methods = new method_info[methods_count]; for (int i = 0; i < methods_count; i++) { methods[i] = new method_info(inputStream); } attributes_count = inputStream.readUnsignedShort(); attributes = new attribute_info[attributes_count]; for (int i = 0; i < attributes_count; i++) { attributes[i] = new attribute_info(inputStream); } } // Build a huge and generally unusable String representation of // ClassFileContents. public String toString() { String result = "["; result += "magic:0x" + hex(magic); result += ",minor_version:0x" + hex(minor_version); result += ",major_version:0x" + hex(major_version); result += ",constant_pool_count:" + constant_pool_count; result += ",constant_pool:["; for (int i = 0; i < constant_pool_count; i++) { result += (i > 0 ? "," : "") + constant_pool[i]; } result += "],access_flags:0x" + hex(access_flags); result += ",this_class:" + constant_pool[((CONSTANT_Class_info) constant_pool[this_class]).name_index]; result += ",super_class:" + constant_pool[((CONSTANT_Class_info) constant_pool[super_class]).name_index]; result += ",interfaces_count:" + interfaces_count; result += ",interfaces:["; for (int i = 0; i < interfaces_count; i++) { result += (i > 0 ? "," : ""); result += constant_pool[interfaces[i]].toString(); } result += "],fields_count:" + fields_count; result += ",fields:["; for (int i = 0; i < fields_count; i++) { result += (i > 0 ? "," : ""); result += fields[i].toString(); } result += "],methods_count:" + methods_count; result += ",methods:["; for (int i = 0; i < methods_count; i++) { result += (i > 0 ? "," : ""); result += methods[i].toString(); } result += "],attributes_count = " + attributes_count; result += ",attributes:["; for (int i = 0; i < attributes_count; i++) { result += (i > 0 ? "," : ""); result += attributes[i].toString(); } result += "]]"; return result; } // Utilities public static String hex(byte n) { final char[] hexdigits = { '0','1','2','3','4','5','6','7', '8','9','a','b','c','d','e','f' }; return "" + hexdigits[(n >> 4) & 0xf] + hexdigits[n & 0xf]; } public static String hex(short n) { return hex((byte)((n >> 8) & 0xff)) + hex((byte)((n) & 0xff)); } public static String hex(int n) { return hex((short)((n >> 16) & 0xffff)) + hex((short)((n) & 0xffff)); } String nameIndex(int n) { String result = "" + n; if (constant_pool.length > n && constant_pool[n].getClass().equals( CONSTANT_Utf8_info.class)) { result += "(" + ((CONSTANT_Utf8_info)constant_pool[n]).getString() + ")"; } return result; } // Utility to decompile Java method signatures public static String decompile(String descriptor) { String result = "", term; int arrayCount = 0; boolean lastKeyWord = false; while (descriptor.length() > 0) { int increment = 1; boolean keyWord = true; switch (descriptor.charAt(0)) { case 'B': term = "byte"; break; case 'C': term = "char"; break; case 'D': term = "double"; break; case 'F': term = "float"; break; case 'I': term = "int"; break; case 'J': term = "long"; break; case 'S': term = "short"; break; case 'V': term = "void"; break; case 'Z': term = "boolean"; break; case 'L': int endLoc = descriptor.indexOf(';'); term = descriptor.substring(1, endLoc); increment = endLoc + 1; break; case '[': term = ""; arrayCount++; break; default: term = descriptor.substring(0, 1); keyWord = false; break; } descriptor = descriptor.substring(increment); if (term.length() > 0) { if (lastKeyWord && keyWord) result += ", "; result += term; for (int i = 0; i < arrayCount; i++) result += "[]"; arrayCount = 0; lastKeyWord = keyWord; } } return result; } // Give this class a main() method... mainly for testing. public static void main(String[] argv) { for (int i = 0; i < argv.length; i++) { ClassFileContents cf = null; FileInputStream is; try { is = new FileInputStream(argv[i]); cf = new ClassFileContents(is); } catch (FileNotFoundException e) { System.err.println(argv[i] + ": " + e); } catch (IOException e) { System.err.println(argv[i] + ": " + e); } System.out.println("\n" + argv[i] + "\n\n" + cf); } } }