Mirror of the BLOAT repository https://www.cs.purdue.edu/homes/hosking/bloat/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
bloat/src/EDU/purdue/cs/bloat/file/ClassFile.java

976 lines
25 KiB

/**
* All files in the distribution of BLOAT (Bytecode Level Optimization and
* Analysis tool for Java(tm)) are Copyright 1997-2001 by the Purdue
* Research Foundation of Purdue University. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package EDU.purdue.cs.bloat.file;
import java.io.*;
import java.util.*;
import EDU.purdue.cs.bloat.reflect.*;
import EDU.purdue.cs.bloat.util.*;
/**
* ClassFile basically represents a Java classfile as it is found on disk. The
* classfile is modeled according to the Java Virtual Machine Specification.
* Methods are provided to edit the classfile at a very low level.
*
* @see Attribute
* @see EDU.purdue.cs.bloat.reflect.Constant
* @see Field
* @see Method
*
* @author Nate Nystrom (<a
* href="mailto:nystrom@cs.purdue.edu">nystrom@cs.purdue.edu</a>)
*/
public class ClassFile implements ClassInfo {
private ClassInfoLoader loader; // ClassInfoLoader that "owns" this class
private List constants; // The constant pool
private int modifiers; // This class's modifer bit field
private int thisClass;
private int superClass;
private int[] interfaces;
private Field[] fields;
private Method[] methods;
private Attribute[] attrs;
private File file; // (.class) File in which this class resides
/**
* Constructor. This constructor parses the class file from the input
* stream.
*
* @param file
* The file in which the class resides.
* @param loader
* The class info loader which loaded the class.
* @param in
* The data stream containing the class.
* @exception ClassFormatError
* When the class could not be parsed.
*/
public ClassFile(final File file, final ClassInfoLoader loader,
final DataInputStream in) {
this.loader = loader;
this.file = file;
// Assert.isTrue(file != null, "Null file for class file");
// Read in file contents from stream
try {
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading header");
}
readHeader(in);
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading constant pool");
}
readConstantPool(in);
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading access flags");
}
readAccessFlags(in);
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading class info");
}
readClassInfo(in);
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading fields");
}
readFields(in);
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading methods");
}
readMethods(in);
if (ClassFileLoader.DEBUG) {
System.out.println("ClassFile: Reading Attributes");
}
readAttributes(in);
in.close();
} catch (final IOException e) {
throw new ClassFormatException(e.getMessage() + " (in file " + file
+ ")");
}
}
/**
* Creates a new <code>ClassFile</code> from scratch. It has no fields or
* methods.
*
* @param modifiers
* The modifiers describing the newly-created class
* @param classIndex
* The index of the type of the newly-created class in its
* constant pool
* @param superClassIndex
* The index of the type of the newly-created class's superclass
* in its constant pool
* @param interfaceIndexes
* The indexes of the types of the interfaces that the
* newly-created class implements
* @param constants
* The constant pool for the newly created class (a list of
* {@link Constant}s).
*/
public ClassFile(final int modifiers, final int classIndex,
final int superClassIndex, final int[] interfaceIndexes,
final List constants, final ClassInfoLoader loader) {
this.modifiers = modifiers;
this.thisClass = classIndex;
this.superClass = superClassIndex;
this.interfaces = interfaceIndexes;
this.constants = constants;
this.loader = loader;
this.fields = new Field[0];
this.methods = new Method[0];
this.attrs = new Attribute[0];
}
/**
* Get the class info loader for the class.
*
* @return The class info loader for the class.
*/
public ClassInfoLoader loader() {
return loader;
}
/**
* Get the name of the class, including the package name.
*
* @return The name of the class.
*/
public String name() {
Constant c = (Constant) constants.get(thisClass);
Assert.isNotNull(c, "Null constant for class name");
if (c.tag() == Constant.CLASS) {
final Integer nameIndex = (Integer) c.value();
if (nameIndex != null) {
c = (Constant) constants.get(nameIndex.intValue());
if (c.tag() == Constant.UTF8) {
return (String) c.value();
}
}
}
throw new ClassFormatException("Couldn't find class name in file");
}
/**
* Set the index into the constant pool of the name of the class.
*
* @param index
* The index of the name of the class.
*/
public void setClassIndex(final int index) {
this.thisClass = index;
}
/**
* Set the index into the constant pool of the name of the class's
* superclass.
*
* @param index
* The index of the name of the superclass.
*/
public void setSuperclassIndex(final int index) {
this.superClass = index;
}
/**
* Set the indices into the constant pool of the names of the class's
* interfaces.
*
* @param indices
* The indices of the names of the interfaces.
*/
public void setInterfaceIndices(final int[] indices) {
this.interfaces = indices;
}
/**
* Get the index into the constant pool of the name of the class.
*
* @return The index of the name of the class.
*/
public int classIndex() {
return thisClass;
}
/**
* Get the index into the constant pool of the name of the class's
* superclass.
*
* @return The index of the name of the superclass.
*/
public int superclassIndex() {
return superClass;
}
/**
* Get the indices into the constant pool of the names of the class's
* interfaces.
*
* @return The indices of the names of the interfaces.
*/
public int[] interfaceIndices() {
return interfaces;
}
/**
* Set the modifiers of the class. The values correspond to the constants in
* the Modifiers class.
*
* @param modifiers
* A bit vector of modifier flags for the class.
* @see Modifiers
*/
public void setModifiers(final int modifiers) {
this.modifiers = modifiers;
}
/**
* Get the modifiers of the class. The values correspond to the constants in
* the Modifiers class.
*
* @return A bit vector of modifier flags for the class.
* @see Modifiers
*/
public int modifiers() {
return modifiers;
}
/**
* Get an array of FieldInfo structures for each field in the class.
*
* @return An array of FieldInfo structures.
*/
public FieldInfo[] fields() {
return fields;
}
/**
* Returns an array of MethodInfo structures for each method in the class.
*/
public MethodInfo[] methods() {
return methods;
}
/**
* Sets the methods in this class.
*/
public void setMethods(final MethodInfo[] methods) {
this.methods = new Method[methods.length];
for (int i = 0; i < methods.length; i++) {
this.methods[i] = (Method) methods[i];
}
}
/**
* Get an array of the constants in the constant pool.
*
* @return An array of Constants.
*/
public Constant[] constants() {
return (Constant[]) constants.toArray(new Constant[0]);
}
/**
* Set all the constants in the constant pool.
*
* @param constants
* The array of Constants.
*/
public void setConstants(final Constant[] constants) {
this.constants = new ArrayList(constants.length);
for (int i = 0; i < constants.length; i++) {
this.constants.add(i, constants[i]);
}
}
/**
* Returns the File from which this <code>ClassFile</code> was created. If
* this <code>ClassFile</code> was created from scratch, <code>null</code>
* is returned.
*/
public File file() {
return file;
}
/**
* Creates a new File object to hold this class. It is placed in the output
* directory and has the name of the class represented by this ClassFile
* followed by the .class extension.
*/
public File outputFile() {
final File outputDir = ((ClassFileLoader) loader).outputDir();
final String fileName = this.name().replace('/', File.separatorChar);
return new File(outputDir, fileName + ".class");
}
/**
* Commit any changes back to a file in the output directory. The output
* directory is determined from the ClassFileLoader.
*/
public void commit() {
try {
commitTo(loader.outputStreamFor(this));
} catch (final IOException e) {
e.printStackTrace();
System.exit(1);
}
}
/**
* Commit changes made to this class. Write changes to an OutputStream.
*/
void commitTo(final OutputStream outStream) {
try {
final DataOutputStream out = new DataOutputStream(outStream);
writeHeader(out);
writeConstantPool(out);
writeAccessFlags(out);
writeClassInfo(out);
writeFields(out, null);
writeMethods(out, null);
writeAttributes(out);
out.close();
} catch (final IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void commitOnly(final Set methods, final Set fields) {
try {
final OutputStream outStream = loader.outputStreamFor(this);
final DataOutputStream out = new DataOutputStream(outStream);
writeHeader(out);
writeConstantPool(out);
writeAccessFlags(out);
writeClassInfo(out);
writeFields(out, fields);
writeMethods(out, methods);
writeAttributes(out);
out.close();
} catch (final IOException e) {
e.printStackTrace();
System.exit(1);
}
}
/**
* Write the class file header.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeHeader(final DataOutputStream out) throws IOException {
out.writeInt(0xCAFEBABE);
out.writeShort(3);
out.writeShort(45);
}
/**
* Write the class's constant pool.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeConstantPool(final DataOutputStream out)
throws IOException {
out.writeShort(constants.size());
// Write the constants. The first constant is reserved for
// internal use by the JVM, so start at 1.
for (int i = 1; i < constants.size(); i++) {
writeConstant(out, (Constant) constants.get(i));
switch (((Constant) constants.get(i)).tag()) {
case Constant.LONG:
case Constant.DOUBLE:
// Longs and doubles take up 2 constant pool entries.
i++;
break;
}
}
}
/**
* Read a constant from the constant pool.
*
* @param in
* The stream from which to read.
* @return The constant.
* @exception IOException
* If an error occurs while reading.
*/
private Constant readConstant(final DataInputStream in) throws IOException {
final int tag = in.readUnsignedByte();
Object value;
switch (tag) {
case Constant.CLASS:
case Constant.STRING:
value = new Integer(in.readUnsignedShort());
break;
case Constant.FIELD_REF:
case Constant.METHOD_REF:
case Constant.INTERFACE_METHOD_REF:
case Constant.NAME_AND_TYPE:
value = new int[2];
((int[]) value)[0] = in.readUnsignedShort();
((int[]) value)[1] = in.readUnsignedShort();
break;
case Constant.INTEGER:
value = new Integer(in.readInt());
break;
case Constant.FLOAT:
value = new Float(in.readFloat());
break;
case Constant.LONG:
// Longs take up 2 constant pool entries.
value = new Long(in.readLong());
break;
case Constant.DOUBLE:
// Doubles take up 2 constant pool entries.
value = new Double(in.readDouble());
break;
case Constant.UTF8:
value = in.readUTF();
break;
default:
throw new ClassFormatException(file.getPath()
+ ": Invalid constant tag: " + tag);
}
return new Constant(tag, value);
}
/**
* Write a constant in the constant pool.
*
* @param out
* The stream to which to write.
* @param constant
* The constant.
* @exception IOException
* If an error occurs while writing.
*/
private void writeConstant(final DataOutputStream out,
final Constant constant) throws IOException {
final int tag = constant.tag();
final Object value = constant.value();
out.writeByte(tag);
switch (tag) {
case Constant.CLASS:
case Constant.STRING:
out.writeShort(((Integer) value).intValue());
break;
case Constant.INTEGER:
out.writeInt(((Integer) value).intValue());
break;
case Constant.FLOAT:
out.writeFloat(((Float) value).floatValue());
break;
case Constant.LONG:
out.writeLong(((Long) value).longValue());
break;
case Constant.DOUBLE:
out.writeDouble(((Double) value).doubleValue());
break;
case Constant.UTF8:
out.writeUTF((String) value);
break;
case Constant.FIELD_REF:
case Constant.METHOD_REF:
case Constant.INTERFACE_METHOD_REF:
case Constant.NAME_AND_TYPE:
out.writeShort(((int[]) value)[0]);
out.writeShort(((int[]) value)[1]);
break;
}
}
/**
* Write the class's access flags.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeAccessFlags(final DataOutputStream out)
throws IOException {
out.writeShort(modifiers);
}
/**
* Write the class's name, superclass, and interfaces.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeClassInfo(final DataOutputStream out) throws IOException {
out.writeShort(thisClass);
out.writeShort(superClass);
out.writeShort(interfaces.length);
for (int i = 0; i < interfaces.length; i++) {
out.writeShort(interfaces[i]);
}
}
/**
* Write the class's fields.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeFields(final DataOutputStream out, final Set onlyFields)
throws IOException {
out.writeShort(fields.length);
for (int i = 0; i < fields.length; i++) {
if ((onlyFields != null) && onlyFields.contains(fields[i])) {
continue;
}
fields[i].write(out);
}
}
/**
* Write the class's methods.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeMethods(final DataOutputStream out, final Set onlyMethods)
throws IOException {
if (onlyMethods != null) {
out.writeShort(onlyMethods.size());
} else {
if (Method.DEBUG) {
System.out.println("Writing " + methods.length + " methods");
}
out.writeShort(methods.length);
}
for (int i = 0; i < methods.length; i++) {
if ((onlyMethods != null) && onlyMethods.contains(methods[i])) {
continue;
}
methods[i].write(out);
}
}
/**
* Write the class's attributes. No attributes are written by this method
* since none are required.
*
* @param out
* The stream to which to write.
* @exception IOException
* If an error occurs while writing.
*/
private void writeAttributes(final DataOutputStream out) throws IOException {
out.writeShort(attrs.length);
for (int i = 0; i < attrs.length; i++) {
out.writeShort(attrs[i].nameIndex());
out.writeInt(attrs[i].length());
attrs[i].writeData(out);
}
}
/**
* Read the class file header.
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*/
private void readHeader(final DataInputStream in) throws IOException {
final int magic = in.readInt();
if (magic != 0xCAFEBABE) {
throw new ClassFormatError("Bad magic number.");
}
in.readUnsignedShort(); // major
in.readUnsignedShort(); // minor
}
/**
* Read the class's constant pool. Constants in the constant pool are
* modeled by an array of <tt>reflect.Constant</tt>/
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*
* @see EDU.purdue.cs.bloat.reflect.Constant
* @see #constants
*/
private void readConstantPool(final DataInputStream in) throws IOException {
final int count = in.readUnsignedShort();
constants = new ArrayList(count);
// The first constant is reserved for internal use by the JVM.
constants.add(0, null);
// Read the constants.
for (int i = 1; i < count; i++) {
constants.add(i, readConstant(in));
switch (((Constant) constants.get(i)).tag()) {
case Constant.LONG:
case Constant.DOUBLE:
// Longs and doubles take up 2 constant pool entries.
constants.add(++i, null);
break;
}
}
}
/**
* Read the class's access flags.
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*/
private void readAccessFlags(final DataInputStream in) throws IOException {
modifiers = in.readUnsignedShort();
}
/**
* Read the class's name, superclass, and interfaces.
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*/
private void readClassInfo(final DataInputStream in) throws IOException {
thisClass = in.readUnsignedShort();
superClass = in.readUnsignedShort();
final int numInterfaces = in.readUnsignedShort();
interfaces = new int[numInterfaces];
for (int i = 0; i < numInterfaces; i++) {
interfaces[i] = in.readUnsignedShort();
}
}
/**
* Read the class's fields.
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*/
private void readFields(final DataInputStream in) throws IOException {
final int numFields = in.readUnsignedShort();
fields = new Field[numFields];
for (int i = 0; i < numFields; i++) {
fields[i] = new Field(in, this);
}
}
/**
* Read the class's methods.
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*/
private void readMethods(final DataInputStream in) throws IOException {
final int numMethods = in.readUnsignedShort();
methods = new Method[numMethods];
for (int i = 0; i < numMethods; i++) {
methods[i] = new Method(in, this);
}
}
/**
* Read the class's attributes. Since none of the attributes are required,
* just read the length of each attribute and skip that many bytes.
*
* @param in
* The stream from which to read.
* @exception IOException
* If an error occurs while reading.
*/
private void readAttributes(final DataInputStream in) throws IOException {
final int numAttributes = in.readUnsignedShort();
attrs = new Attribute[numAttributes];
for (int i = 0; i < numAttributes; i++) {
final int nameIndex = in.readUnsignedShort();
final int length = in.readInt();
attrs[i] = new GenericAttribute(in, nameIndex, length);
}
}
/**
* Creates a new field in this classfile
*/
public FieldInfo addNewField(final int modifiers, final int typeIndex,
final int nameIndex) {
final Field field = new Field(this, modifiers, typeIndex, nameIndex);
// Add the new field to the list of fields
final Field[] fields = new Field[this.fields.length + 1];
for (int i = 0; i < this.fields.length; i++) {
fields[i] = this.fields[i];
}
fields[this.fields.length] = field;
this.fields = fields;
return (field);
}
/**
* Creates a new field in this classfile
*/
public FieldInfo addNewField(final int modifiers, final int typeIndex,
final int nameIndex, final int cvNameIndex,
final int constantValueIndex) {
final Field field = new Field(this, modifiers, typeIndex, nameIndex,
cvNameIndex, constantValueIndex);
// Add the new field to the list of fields
final Field[] fields = new Field[this.fields.length + 1];
for (int i = 0; i < this.fields.length; i++) {
fields[i] = this.fields[i];
}
fields[this.fields.length] = field;
this.fields = fields;
return (field);
}
/**
* Removes the field whose name is at the given index in the constant pool.
*
* @throws IllegalArgumentException The class does not contain a field whose
* name is at the given index
*/
public void deleteField(final int nameIndex) {
final List newFields = new ArrayList();
boolean foundIt = false;
for (int i = 0; i < this.fields.length; i++) {
final Field field = this.fields[i];
if (field.nameIndex() == nameIndex) {
foundIt = true;
} else {
newFields.add(field);
}
}
if (!foundIt) {
final String s = "No field with name index " + nameIndex + " in "
+ this.name();
throw new IllegalArgumentException(s);
} else {
this.fields = (Field[]) newFields.toArray(new Field[0]);
}
}
/**
* Deletes a method from this class
*
* @param nameIndex
* Index in the constant pool of the name of the method to be
* deleted
* @param typeIndex
* Index in the constant pool of the type of the method to be
* deleted
*
* @throws IllegalArgumentException The class modeled by this
* <code>ClassInfo</code> does not contain a method whose name and
* type are not at the given indices
*/
public void deleteMethod(final int nameIndex, final int typeIndex) {
final List newMethods = new ArrayList();
boolean foundIt = false;
for (int i = 0; i < this.methods.length; i++) {
final Method method = this.methods[i];
if ((method.nameIndex() == nameIndex)
&& (method.typeIndex() == typeIndex)) {
foundIt = true;
} else {
newMethods.add(method);
}
}
if (!foundIt) {
final String s = "No method with name index " + nameIndex
+ " and type index " + typeIndex + " in " + this.name();
throw new IllegalArgumentException(s);
} else {
this.methods = (Method[]) newMethods.toArray(new Method[0]);
}
}
/**
* Creates a new method in this class
*/
public MethodInfo addNewMethod(final int modifiers, final int typeIndex,
final int nameIndex, final int exceptionIndex,
final int[] exceptionTypeIndices, final int codeIndex) {
final Exceptions exceptions = new Exceptions(this, exceptionIndex,
exceptionTypeIndices);
final Code code = new Code(this, codeIndex); // code can't be null
final Attribute[] attributes = new Attribute[] { exceptions, code };
final Method method = new Method(this, modifiers, nameIndex, typeIndex,
attributes, code, exceptions);
// Add the new method to the list of method
final Method[] methods = new Method[this.methods.length + 1];
for (int i = 0; i < this.methods.length; i++) {
methods[i] = this.methods[i];
}
methods[this.methods.length] = method;
this.methods = methods;
return (method);
}
/**
* Prints a textual representation of this classfile to a PrintStream. The
* text includes information such as the constants in the constant pool, the
* name of the class's superclass, its modifier flags, its fields, and its
* methods.
*
* @param out
* The stream to which to print.
*/
public void print(final PrintStream out) {
print(new PrintWriter(out, true));
}
public void print(final PrintWriter out) {
out.print("(constants");
for (int i = 0; i < constants.size(); i++) {
out.print("\n " + i + ": " + constants.get(i));
}
out.println(")");
out.println("(class " + classIndex() + ")");
out.println("(super " + superclassIndex() + ")");
out.print("(interfaces");
for (int i = 0; i < interfaces.length; i++) {
out.print("\n " + i + ": " + interfaces[i]);
}
out.println(")");
out.print("(modifiers");
if ((modifiers & Modifiers.PUBLIC) != 0) {
out.print(" PUBLIC");
}
if ((modifiers & Modifiers.FINAL) != 0) {
out.print(" FINAL");
}
if ((modifiers & Modifiers.SUPER) != 0) {
out.print(" SUPER");
}
if ((modifiers & Modifiers.INTERFACE) != 0) {
out.print(" INTERFACE");
}
if ((modifiers & Modifiers.ABSTRACT) != 0) {
out.print(" ABSTRACT");
}
out.println(")");
out.print("(fields");
for (int i = 0; i < fields.length; i++) {
out.print("\n " + i + ": " + fields[i]);
}
out.println(")");
out.print("(methods");
for (int i = 0; i < methods.length; i++) {
out.print("\n " + i + ": " + methods[i]);
}
out.println(")");
}
public String toString() {
return "(ClassFile " + name() + ")";
}
}