soot.coffi.ClassFile Maven / Gradle / Ivy
/* Soot - a J*va Optimization Framework
* Copyright (C) 1997 Clark Verbrugge
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the Sable Research Group and others 1997-1999.
* See the 'credits' file distributed with Soot for the complete list of
* contributors. (Soot is distributed at http://www.sable.mcgill.ca/soot)
*/
package soot.coffi;
import soot.*;
import java.io.*;
/**
* A ClassFile object represents the contents of a .class file.
*
* A ClassFile contains code for manipulation of its constituents.
* @author Clark Verbrugge
*/
public class ClassFile {
/** Magic number. */
static final long MAGIC = 0xCAFEBABEL;
/** Access bit flag. */
static final short ACC_PUBLIC = 0x0001;
/** Access bit flag. */
static final short ACC_PRIVATE = 0x0002;
/** Access bit flag. */
static final short ACC_PROTECTED = 0x0004;
/** Access bit flag. */
static final short ACC_STATIC = 0x0008;
/** Access bit flag. */
static final short ACC_FINAL = 0x0010;
/** Access bit flag. */
static final short ACC_SUPER = 0x0020;
/** Access bit flag. */
static final short ACC_VOLATILE = 0x0040;
/** Access bit flag. */
static final short ACC_TRANSIENT = 0x0080;
/** Access bit flag. */
static final short ACC_INTERFACE = 0x0200;
/** Access bit flag. */
static final short ACC_ABSTRACT = 0x0400;
/** Access bit flag. */
static final short ACC_STRICT = 0x0800;
/** Access bit flag. */
static final short ACC_ANNOTATION = 0x2000;
/** Access bit flag. */
static final short ACC_ENUM = 0x4000;
/** Remaining bits in the access bit flag. */
static final short ACC_UNKNOWN = 0x7000;
/** Descriptor code string. */
static final String DESC_BYTE = "B";
/** Descriptor code string. */
static final String DESC_CHAR = "C";
/** Descriptor code string. */
static final String DESC_DOUBLE = "D";
/** Descriptor code string. */
static final String DESC_FLOAT= "F";
/** Descriptor code string. */
static final String DESC_INT = "I";
/** Descriptor code string. */
static final String DESC_LONG = "J";
/** Descriptor code string. */
static final String DESC_OBJECT = "L";
/** Descriptor code string. */
static final String DESC_SHORT = "S";
/** Descriptor code string. */
static final String DESC_BOOLEAN = "Z";
/** Descriptor code string. */
static final String DESC_VOID = "V";
/** Descriptor code string. */
static final String DESC_ARRAY = "[";
/** Debugging flag. */
boolean debug;
/** File name of the .class this represents. */
String fn;
/* For chaining ClassFiles into a list.
ClassFile next;*/
/** Magic number read in.
* @see ClassFile#MAGIC
*/
long magic;
/** Minor version. */
int minor_version;
/** Major version. */
int major_version;
/** Number of items in the constant pool. */
public int constant_pool_count;
/** Array of constant pool items.
* @see cp_info
*/
public cp_info constant_pool[];
/** Access flags for this Class.
*/
public int access_flags;
/** Constant pool index of the Class constant describing this.
* @see CONSTANT_Class_info
*/
public int this_class;
/** Constant pool index of the Class constant describing super.
* @see CONSTANT_Class_info
*/
public int super_class;
/** Count of interfaces implemented. */
public int interfaces_count;
/** Array of constant pool indices of Class constants describing each
* interace implemented by this class, as given in the source for this
* class.
* @see CONSTANT_Class_info
*/
public int interfaces[];
/** Count of fields this Class contains. */
public int fields_count;
/** Array of field_info objects describing each field.
* @see field_info
*/
public field_info fields[];
/** Count of methods this Class contains. */
public int methods_count;
/** Array of method_info objects describing each field.
* @see method_info
*/
public method_info methods[];
/** Count of attributes this class contains. */
public int attributes_count;
/** Array of attribute_info objects for this class.
* @see attribute_info
*/
attribute_info attributes[];
/** Creates a new ClassFile object given the name of the file.
* @param nfn file name which this ClassFile will represent.
*/
public ClassFile(String nfn) { fn = nfn; }
/** Returns the name of this Class. */
public String toString() {
return (constant_pool[this_class].toString(constant_pool));
}
public boolean loadClassFile(InputStream is)
{
DataInputStream d = null;
try {
d = new DataInputStream(new BufferedInputStream(is, 8192));
return readClass(d);
} finally {
try {
d.close();
} catch (Throwable t) {}
}
}
/** Main entry point for writing a class file.
* The file name is given in the constructor; this opens the
* file and writes the internal representation.
* @return true on success.
*/
boolean saveClassFile() {
FileOutputStream f;
DataOutputStream d;
boolean b;
try {
f = new FileOutputStream(fn);
} catch(FileNotFoundException e) {
if (fn.indexOf(".class")>=0) {
G.v().out.println("Can't find " + fn);
return false;
}
fn = fn + ".class";
try {
f = new FileOutputStream(fn);
} catch(FileNotFoundException ee) {
G.v().out.println("Can't find " + fn);
return false;
}
}
d = new DataOutputStream(f);
if (d==null) {
try {
f.close();
} catch(IOException e) { }
return false;
}
b = writeClass(d);
try {
d.close();
f.close();
} catch(IOException e) {
G.v().out.println("IOException with " + fn + ": " + e.getMessage());
return false;
}
return b;
}
/** Returns a String constructed by parsing the bits in the given
* access code (as defined by the ACC_* constants).
* @param af access code.
* @param separator String used to separate words used for access bits.
* @see ClassFile#access_flags
* @see method_info#access_flags
* @see field_info#access_flags
*/
static String access_string(int af,String separator) {
boolean hasone = false;
String s = "";
if ((af & ACC_PUBLIC) != 0) {
s = "public";
hasone = true;
}
if ((af & ACC_PRIVATE) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "private";
}
if ((af & ACC_PROTECTED) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "protected";
}
if ((af & ACC_STATIC) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "static";
}
if ((af & ACC_FINAL) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "final";
}
if ((af & ACC_SUPER) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "super";
}
if ((af & ACC_VOLATILE) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "volatile";
}
if ((af & ACC_TRANSIENT) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "transient";
}
if ((af & ACC_INTERFACE) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "interface";
}
if ((af & ACC_ABSTRACT) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "abstract";
}
if ((af & ACC_STRICT) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "strict";
}
if ((af & ACC_ANNOTATION) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "annotation";
}
if ((af & ACC_ENUM) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "enum";
}
if ((af & ACC_UNKNOWN) != 0) {
if (hasone) s = s + separator;
else hasone = true;
s = s + "unknown";
}
return s;
}
/** Builds the internal representation of this Class by reading in the
* given class file.
* @param d Stream forming the .class file.
* @return true if read was successful, false on some error.
*/
public boolean readClass(DataInputStream d) {
try {
// first read in magic number
magic = d.readInt() & 0xFFFFFFFFL;
if (magic != MAGIC) {
G.v().out.println("Wrong magic number in " + fn + ": " + magic);
return false;
}
//G.v().out.println("Magic number ok");
minor_version = d.readUnsignedShort();
major_version = d.readUnsignedShort();
// G.v().out.println("Version: " + major_version + "." + minor_version);
constant_pool_count = d.readUnsignedShort();
//G.v().out.println("Constant pool count: " + constant_pool_count);
if (!readConstantPool(d))
return false;
access_flags = d.readUnsignedShort();
/*if (access_flags!=0)
G.v().out.println("Access flags: " + access_flags + " = " +
access_string(access_flags,", "));*/
this_class = d.readUnsignedShort();
super_class = d.readUnsignedShort();
interfaces_count = d.readUnsignedShort();
if (interfaces_count>0) {
interfaces = new int[interfaces_count];
int j;
for (j=0; j0) {
attributes = new attribute_info[attributes_count];
readAttributes(d,attributes_count,attributes);
}
Timers.v().attributeTimer.end();
} catch(IOException e) {
throw new RuntimeException("IOException with " + fn + ": " + e.getMessage());
}
/*inf.fields = fields_count;
inf.methods = methods_count;
inf.cp = constant_pool_count;*/
return true;
}
/** Reads in the constant pool from the given stream.
* @param d Stream forming the .class file.
* @return true if read was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean readConstantPool(DataInputStream d) throws IOException {
byte tag;
cp_info cp;
int i;
boolean skipone; // set if next cp entry is to be skipped
constant_pool = new cp_info[constant_pool_count];
//Instruction.constant_pool = constant_pool;
skipone = false;
for (i=1;i.class file.
* @param attributes_count number of attributes to read in.
* @param ai pre-allocated array of attributes to be filled in.
* @return true if read was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean readAttributes(DataInputStream d,int attributes_count,
attribute_info[] ai) throws IOException {
attribute_info a=null;
int i;
int j;
long len;
String s;
for (i=0;i0) {
int k;
ea.exception_index_table = new int[ea.number_of_exceptions];
for (k=0; k0) {
ga.info = new byte[(int) len];
d.read(ga.info);
}
a = (attribute_info)ga;
}
a.attribute_name = j;
a.attribute_length = len;
ai[i] = a;
}
return true;
}
private element_value [] readElementValues(int count, DataInputStream d, boolean needName, int name_index)
throws IOException {
element_value [] list = new element_value[count];
for (int x = 0; x < count; x++){
if (needName){
name_index = d.readUnsignedShort();
}
int tag = d.readUnsignedByte();
char kind = (char)tag;
if (kind == 'B' || kind == 'C' || kind == 'D' || kind == 'F' || kind == 'I' || kind == 'J' || kind == 'S' || kind == 'Z' || kind == 's'){
constant_element_value elem = new constant_element_value();
elem.name_index = name_index;
elem.tag = kind;
elem.constant_value_index = d.readUnsignedShort();
list[x] = elem;
}
else if (kind == 'e'){
enum_constant_element_value elem = new enum_constant_element_value();
elem.name_index = name_index;
elem.tag = kind;
elem.type_name_index = d.readUnsignedShort();
elem.constant_name_index = d.readUnsignedShort();
list[x] = elem;
}
else if (kind == 'c'){
class_element_value elem = new class_element_value();
elem.name_index = name_index;
elem.tag = kind;
elem.class_info_index = d.readUnsignedShort();
list[x] = elem;
}
else if (kind == '['){
array_element_value elem = new array_element_value();
elem.name_index = name_index;
elem.tag = kind;
elem.num_values = d.readUnsignedShort();
elem.values = readElementValues(elem.num_values, d, false, name_index);
list[x] = elem;
}
else if (kind == '@'){
annotation_element_value elem = new annotation_element_value();
elem.name_index = name_index;
elem.tag = kind;
annotation annot = new annotation();
annot.type_index = d.readUnsignedShort();
annot.num_element_value_pairs = d.readUnsignedShort();
annot.element_value_pairs = readElementValues(annot.num_element_value_pairs, d, true, 0);
elem.annotation_value = annot;
list[x] = elem;
}
else {
throw new RuntimeException("Unknown element value pair kind: "+kind);
}
}
return list;
}
/** Reads in the fields from the given stream.
* @param d Stream forming the .class file.
* @return true if read was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean readFields(DataInputStream d) throws IOException {
field_info fi;
int i;
fields = new field_info[fields_count];
for (i=0;i0) {
fi.attributes = new attribute_info[fi.attributes_count];
readAttributes(d,fi.attributes_count,fi.attributes);
}
/*CONSTANT_Utf8_info ci;
ci = (CONSTANT_Utf8_info)(constant_pool[fi.name_index]);
G.v().out.println("Field: " + ci.convert());*/
fields[i] = fi;
}
return true;
}
/** Reads in the methods from the given stream.
* @param d Stream forming the .class file.
* @return true if read was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean readMethods(DataInputStream d) throws IOException {
method_info mi;
int i;
methods = new method_info[methods_count];
for (i=0;i0) {
mi.attributes = new attribute_info[mi.attributes_count];
readAttributes(d,mi.attributes_count,mi.attributes);
for (int j=0; jtrue if write was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean writeConstantPool(DataOutputStream dd) throws IOException {
byte tag;
cp_info cp;
int i;
boolean skipone = false;
for (i=1;itrue if write was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean writeAttributes(DataOutputStream dd, int attributes_count,
attribute_info[] ai) throws IOException {
attribute_info a=null;
int i,len;
short j;
String s;
for (i=0;i0)
writeAttributes(dd,ca.attributes_count,ca.attributes);
} else if(a instanceof Exception_attribute) {
Exception_attribute ea = (Exception_attribute)a;
dd.writeShort(ea.number_of_exceptions);
if (ea.number_of_exceptions>0) {
int k;
for (k=0; k0) {
dd.write(ga.info,0,(int) ga.attribute_length);
}
}
}
return true;
}
/** Writes the fields to the given stream.
* @param dd output stream.
* @return true if write was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean writeFields(DataOutputStream dd) throws IOException {
field_info fi;
int i;
for (i=0;i0) {
writeAttributes(dd,fi.attributes_count,fi.attributes);
}
}
return true;
}
/** Writes the methods to the given stream.
* @param dd output stream.
* @return true if write was successful, false on some error.
* @exception java.io.IOException on error.
*/
protected boolean writeMethods(DataOutputStream dd) throws IOException {
method_info mi;
int i;
for (i=0;i0) {
writeAttributes(dd,mi.attributes_count,mi.attributes);
}
}
return true;
}
/** Writes this entire ClassFile object to the given stream.
* @param dd output stream.
* @return true if write was successful, false on some error.
*/
boolean writeClass(DataOutputStream dd) {
// outputs the .class file from the loaded one
try {
// first write magic number
dd.writeInt((int) magic);
dd.writeShort(minor_version);
dd.writeShort(major_version);
dd.writeShort(constant_pool_count);
if (!writeConstantPool(dd))
return false;
dd.writeShort(access_flags);
dd.writeShort(this_class);
dd.writeShort(super_class);
dd.writeShort(interfaces_count);
if (interfaces_count>0) {
int j;
for (j=0; j0) {
writeAttributes(dd,attributes_count,attributes);
}
} catch(IOException e) {
G.v().out.println("IOException with " + fn + ": " + e.getMessage());
return false;
}
return true;
}
/** Parses the given method, converting its bytecode array into a list
* of Instruction objects.
* @param m method to parse.
* @return head of a list of Instructions.
* @see Instruction
* @see ByteCode
* @see ByteCode#disassemble_bytecode
*/
public Instruction parseMethod(method_info m) {
// first task, look through attributes for a code attribute
int j;
Code_attribute ca;
ByteCode bc;
Instruction inst,head,tail;
exception_table_entry e;
head = null;
tail = null;
bc = new ByteCode();
ca = m.locate_code_attribute();
if (ca==null) return null;
j = 0;
while(jnull on error.
* @see CFG#reconstructInstructions
* @see ClassFile#parseMethod
* @see ClassFile#relabel
* @see Instruction#compile
*/
byte[] unparseMethod(method_info m) {
int codesize;
byte bc[];
Instruction i;
// Rebuild instruction sequence
m.cfg.reconstructInstructions();
// relabel instructions and get size of code array
codesize = relabel(m.instructions);
// construct a new array for the byte-code
bc = new byte[codesize];
if (bc==null) {
G.v().out.println("Warning: can't allocate memory for recompile");
return null;
}
// then recompile the instructions into byte-code
i = m.instructions;
codesize = 0;
while (i!=null) {
codesize = i.compile(bc,codesize);
i = i.next;
}
if (codesize != bc.length)
G.v().out.println("Warning: code size doesn't match array length!");
return bc;
}
/** Inversive to parse, this method calls unparseMethod for each
* method, storing the resulting bytecode in the method's code
* attribute, and recomputing offsets for exception handlers.
* @see ClassFile#unparseMethod
*/
void unparse() {
int i,j;
Code_attribute ca;
byte bc[];
method_info mi;
exception_table_entry e;
for (i=0;i=0) {
return parseDesc(s.substring(j+1),",");
}
return parseDesc(s,",");
}
/** Static utility method to parse the given method descriptor string.
* @param s descriptor string.
* @return comma-separated ordered list of parameter types
* @see ClassFile#parseDesc
* @see ClassFile#parseMethodDesc_return
*/
static String parseMethodDesc_params(String s) {
int i,j;
i = s.indexOf('(');
if (i>=0) {
j = s.indexOf(')',i+1);
if (j>=0) {
return parseDesc(s.substring(i+1,j),",");
}
}
return "";
}
/** Static utility method to parse the given method descriptor string.
* @param desc descriptor string.
* @param sep String to use as a separator between types.
* @return String of types parsed.
* @see ClassFile#parseDesc
* @see ClassFile#parseMethodDesc_return
*/
static String parseDesc(String desc,String sep) {
String params = "",param;
char c;
int i,len,arraylevel=0;
boolean didone = false;
len = desc.length();
for (i=0;i10 && desc.substring(i+1,i+11).compareTo("java/lang/")==0)
i = i+10;
param = desc.substring(i+1,j);
// replace '/'s with '.'s
param = param.replace('/','.');
i = j;
}
} else {
param = "???";
}
if (didone) params = params + sep;
params = params + param;
while (arraylevel>0) {
params = params + "[]";
arraylevel--;
}
didone = true;
}
return params;
}
/** Locates a method by name.
* @param s name of method.
* @return method_info object representing method, or null if not found.
* @see method_info#toName
*/
method_info findMethod(String s) {
method_info m;
int i;
for (i=0;ipos) {
for (j=i;j>pos && j>0;j--)
methods[j] = methods[j-1];
methods[pos] = mthd;
} else if (itrue if it is a parent, false otherwise.
* @see ClassFile#descendsFrom(String)
*/
boolean descendsFrom(ClassFile cf) { return descendsFrom(cf.toString()); }
/** Answers whether this class is an immediate descendant (as subclass or
* as an implementation of an interface) of the given class.
* @param cname name of supposed parent.
* @return true if it is a parent, false otherwise.
* @see ClassFile#descendsFrom(ClassFile)
*/
boolean descendsFrom(String cname) {
cp_info cf;
int i;
cf = constant_pool[super_class];
if (cf.toString(constant_pool).compareTo(cname)==0) return true;
for (i=0;itrue if it cannot, false if it might.
*/
boolean isSterile() {
if ((access_flags&ACC_PUBLIC)!=0 && (access_flags&ACC_FINAL)==0) return false;
return true;
}
/** Given the name of a class --- possibly with .class after it,
* this answers whether the class might refer to this ClassFile object.
* @return true if it does, false if it doesn't.
*/
boolean sameClass(String cfn) {
String s = cfn;
int i = s.lastIndexOf(".class");
if (i>0) { // has .class after it
s = s.substring(0,i); // cut off the .class
}
if (s.compareTo(toString())==0)
return true;
return false;
}
/** Returns the name of a specific field in the field array.
* @param i index of field in field array.
* @return name of field.
*/
String fieldName(int i) {
return fields[i].toName(constant_pool);
}
/* DEPRECATED
// Locates the given classfile, and extracts it from the list.
// It cannot be the first one in the list, and this returns null
// or the classfile.
static ClassFile removeClassFile(ClassFile cfhead,String cfn) {
ClassFile cf,cfprev;
cf = cfhead;
cfprev = null;
while (cf!=null) {
if (cf.sameClass(cfn)) {
if (cfprev==null) return null; // this shouldn't happen
cfprev.next = cf.next;
cf.next = null;
return cf;
}
cfprev = cf;
cf = cf.next;
}
return null;
}
// returns true if this class contains any references to the given
// cuClass.cuName, which is of type cuDesc. Searches for methods if
// ismethod is true, fields otherwise.
boolean refersTo(boolean ismethod,CONSTANT_Utf8_info cuClass,
CONSTANT_Utf8_info cuName,CONSTANT_Utf8_info cuDesc) {
int i;
CONSTANT_Utf8_info cu;
// note that we start at 1 in the constant pool
if (ismethod) {
for (i=1;i=1;largest--) {
for (i=0;ia[largest]) {
s = a[i];
a[i] = a[largest];
a[largest] = s;
}
}
}
return a;
}
// Given a new constant pool, and a list of redirections
// (new index = redirect[old index]), this changes all constant
// pool entries, and installs the new constant pool of size size
void changeConstantPool(short redirect[],cp_info newCP[],short size) {
Debig d = new Debig(this);
d.redirectCPRefs(redirect);
constant_pool = newCP;
constant_pool_count = size;
}
// the constant pool is typically a few hundred entries in size, and so
// is just a bit too big to make use of insertion/selection sort.
// However, the variable size of the entries makes using a heapsort
// or quicksort rather cumbersome, so since it is quite close to the
// limits of efficient insertion/selection sort, we'll use that anyway.
void sortConstantPool() {
cp_info newcp[] = new cp_info[constant_pool_count];
short redirect[] = new short[constant_pool_count];
newcp[0] = constant_pool[0]; // the 0-entry stays put
redirect[0] = (short)0;
int smallest,j;
for (int i=1;i " + i);
if (constant_pool[smallest].tag==cp_info.CONSTANT_Double ||
constant_pool[smallest].tag==cp_info.CONSTANT_Long) {
redirect[++smallest] = (short)(++i);
newcp[i] = constant_pool[smallest];
}
}
// constant pool is now sorted into newcp
changeConstantPool(redirect,newcp,constant_pool_count);
G.v().out.println("Finished sorting constant pool");
}
// just a wrapper for the debigulation, so we can elegantly allocate
// a new debigulator, debigualte and then produce some output
void debigulate(boolean attribs,boolean privates) {
Debig debigulator = new Debig(this);
debigulator.debigulate(attribs,privates);
debigulator.setCF(null);
inf.verboseReport(G.v().out);
}*/
}