jreversepro.parser.JClassParser Maven / Gradle / Ivy
/*
* @(#)JClassParser.java
*
* JReversePro - Java Decompiler / Disassembler.
* Copyright (C) 2000 2001 Karthik Kumar.
* EMail: [email protected]
*
* 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.
**/
package jreversepro.parser;
import jreversepro.common.AppConstants;
import jreversepro.reflect.JClassInfo;
import jreversepro.reflect.JConstantPool;
import jreversepro.reflect.JField;
import jreversepro.reflect.JMethod;
import java.io.*;
import java.net.URL;
//import jreversepro.common.Helper;
/**
* @author Karthik Kumar
*/
public class JClassParser implements AppConstants {
/**
* ConstantPool Information of the class being reverse engineered.
*/
private JConstantPool mCpInfo;
/**
* DataInputStream containing the bytes of the class.
*/
private DataInputStream mDis;
/**
* Information about fields, methods of the class being reverse engineered.
*/
private JClassInfo mInfoClass;
/**
* @return Returns the data Information of the class.
*/
public final JClassInfo getClassInfo() {
return mInfoClass;
}
/**
* @return Returns the ConstantPool Information of the class.
*/
public final JConstantPool getCpInfo() {
return mCpInfo;
}
/**
* Parses the given file and creates the ClassInfo and ConstantPool objects.
*
* @param aFile class file to be parsed.
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
public void parse(File aFile)
throws IOException, ClassParserException {
if (aFile.getName().endsWith(".class")) {
try {
// Dangerous conversion of long to int, but nevertheless
//all class file streams happen to come under this limit.
parse(new FileInputStream(aFile), (int) aFile.length(), aFile.toString());
} catch (FileNotFoundException eFNFE) {
throw new ClassParserException("Class file to reverse engineer " + aFile.toString() + " not found");
}
} else {
throw new ClassParserException(aFile.toString() + " does not have .class extension ");
}
}
/**
* Parses the given file and creates the ClassInfo and ConstantPool objects.
*
* @param is InputStream from which bytes are taken.
* @param length Length of the bytecode stream.
* @param pathToClass path to class.
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
public void parse(InputStream is, int length, String pathToClass)
throws IOException, ClassParserException {
if (is instanceof ByteArrayInputStream) {
parse((ByteArrayInputStream) is, pathToClass);
//perform Explicit Casting - safely.
} else {
ByteArrayInputStream bais;
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
byte[] buf = new byte[length]; // length becomes just a guess
int nread;
while ((nread = is.read(buf, 0, buf.length)) > 0) {
baos.write(buf, 0, nread);
}
bais = new ByteArrayInputStream(baos.toByteArray());
parse(bais, pathToClass);
//Convert to bytestream first since once brought to
//memory operations will be easy to perform.
}
}
/**
* Parses the given file and creates the ClassInfo and ConstantPool objects.
*
* @param is InputStream containing the bytes.
* @param pathToClass path to the class.
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
public void parse(ByteArrayInputStream is, String pathToClass)
throws IOException, ClassParserException {
mCpInfo = null;
mInfoClass = null;
mInfoClass = new JClassInfo();
mDis = new DataInputStream(is);
mInfoClass.setPathName(pathToClass);
readMagic();
readVersion();
readConstantPool();
readAccess();
fillThisClass();
fillSuperClass();
readInterfaces();
readFields();
readMethods();
readAttributes();
mDis.close();
mInfoClass.setConstantPool(mCpInfo);
}
/**
* Reads the Magic number.
*
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readMagic()
throws ClassParserException, IOException {
int magic = mDis.readInt();
if (magic != MAGIC) {
throw new ClassParserException("Invalid Magic Number");
}
}
/**
* Reads the Version of the class file.
*
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readVersion()
throws ClassParserException, IOException {
short minor = mDis.readShort();
short major = mDis.readShort();
if (!supportedMajorMinor(major, minor)) {
throw new ClassParserException("(Major: " + major + ", Minor: " + minor + ") version not supported");
}
mInfoClass.setMajorMinor(major, minor);
}
/**
* Reads the ConstantPool.
*
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readConstantPool()
throws IOException, ClassParserException {
int numCpEntry = mDis.readShort();
mCpInfo = new JConstantPool(numCpEntry);
readCpEntries(numCpEntry);
}
/**
* Reads the access specifier of the class.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readAccess()
throws IOException {
mInfoClass.setAccess(mDis.readShort());
}
/**
* Reads the fully qualified name of the current class.
*
For Example , a class by name JClassParser
* in the package Heart
would be read as:
* Heart/JClassParser
.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void fillThisClass()
throws IOException {
mInfoClass.setThisClass(mCpInfo.getClassName(mDis.readShort()));
}
/**
* Reads the fully qualified name of the super class.
*
For Example , a class by name JClassParser
* in the package Heart
would be read as:
* Heart/JClassParser
.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void fillSuperClass()
throws IOException {
mInfoClass.setSuperClass(mCpInfo.getClassName(mDis.readShort()));
}
/**
* Reads the fully qualified name of the interfaces
* implemented
by the Current class.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @see JClassInfo#addInterface(String)
*/
private void readInterfaces()
throws IOException {
short count = mDis.readShort();
for (int i = 0; i < count; i++) {
String interfaceadd = mCpInfo.getClassName(mDis.readShort());
mInfoClass.addInterface(interfaceadd);
}
}
/**
* Reads the fields defined
in the Current class.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @throws ClassParserException Thrown in case of an invalid
* constantpool reference.
* @see JField
*/
private void readFields()
throws IOException, ClassParserException {
short count = mDis.readShort();
for (int i = 0; i < count; i++) {
JField curField = new JField();
short accessFlags = mDis.readShort();
short nameIndex = mDis.readShort();
short descIndex = mDis.readShort();
String name = mCpInfo.getUtf8String(nameIndex);
String descriptor = mCpInfo.getUtf8String(descIndex);
curField.setName(name);
curField.setDatatype(descriptor);
curField.setQualifier(accessFlags);
short attrCount = mDis.readShort();
for (int j = 0; j < attrCount; j++) {
readFieldAttributes(curField);
}
mInfoClass.addField(curField);
}
}
/**
* Reads the methods defined
in the Current class.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @see JMethod
*/
private void readMethods()
throws IOException {
short count = mDis.readShort();
for (int i = 0; i < count; i++) {
JMethod curMethod = new JMethod(mInfoClass);
short accessFlags = mDis.readShort();
short nameIndex = mDis.readShort();
short descIndex = mDis.readShort();
String name = mCpInfo.getUtf8String(nameIndex);
String descriptor = mCpInfo.getUtf8String(descIndex);
curMethod.setName(name);
curMethod.setSignature(descriptor);
curMethod.setQualifier(accessFlags);
short attrCount = mDis.readShort();
for (int j = 0; j < attrCount; j++) {
readMethodAttributes(curMethod);
}
mInfoClass.addMethod(curMethod);
}
}
/**
* Reads the ATTRIBUTES of the fields and methods.
*
The possible attributes here are
* Code, LineNumberTable, Exception .
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @see JAttribute
*/
private void readAttributes()
throws IOException {
short attrCount = mDis.readShort();
for (int i = 0; i < attrCount; i++) {
readClassAttributes();
}
}
/**
* @param major Major version of the JVM.
* @param minor Minor version of the JVM.
* test whether or not the supplied major/minor class
* versions are acceptable. (NOTE: we should probably
* have a runtime switch to accept all versions since
* even though the versions may be incremented, there are
* typically no changes to the form of the class files)
*
* 45.3+ is java 1.1
* 46.0 is java 1.2
* 47.0 is java 1.3
* 48.0 is java 1.4
* @return true if the major/minor versions are acceptable
*/
private boolean supportedMajorMinor(short major, short minor) {
if (major == 45) {
return (minor >= 3);
}
return major >= 46 && major <= 48;
}
/**
* Reads the ConstantPool Entries.
*
* @param aNumEntry Number of constant pool entries.
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readCpEntries(int aNumEntry)
throws IOException, ClassParserException {
mCpInfo.addNullEntry();
for (int i = 1; i < aNumEntry; i++) {
byte tagByte = mDis.readByte();
switch (tagByte) {
case JConstantPool.TAG_UTF8:
readTagUtf8(i);
break;
case JConstantPool.TAG_INTEGER:
readTagInteger(i);
break;
case JConstantPool.TAG_FLOAT:
readTagFloat(i);
break;
case JConstantPool.TAG_LONG:
readTagLong(i);
//Long takes two ConstantPool Entries.
i++;
break;
case JConstantPool.TAG_DOUBLE:
readTagDouble(i);
//Double takes two ConstantPool Entries.
i++;
break;
case JConstantPool.TAG_CLASS:
readTagClass(i);
break;
case JConstantPool.TAG_STRING:
readTagString(i);
break;
case JConstantPool.TAG_FIELDREF:
readTagFieldRef(i);
break;
case JConstantPool.TAG_METHODREF:
readTagMethodRef(i);
break;
case JConstantPool.TAG_INTERFACEREF:
readTagInterfaceRef(i);
break;
case JConstantPool.TAG_NAMETYPE:
readTagNameType(i);
break;
default:
throw new ClassParserException("TagByte " + tagByte + " Invalid for ConstantPool Entry #" + i);
}
}
}
/**
* Reads the ATTRIBUTES of the field defined in the Current class.
*
The possible attributes here are
* ConstantValue , Deprecated , Synthetic. .
*
* @param aRhsField Reads attributes into this field.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @throws ClassParserException Thrown in case of an invalid
* constantpool reference.
* @see JAttribute
*/
private void readFieldAttributes(JField aRhsField)
throws IOException, ClassParserException {
String attrName = mCpInfo.getUtf8String(mDis.readShort());
if (attrName.compareTo(JAttribute.CONSTANT_VALUE) == 0) {
aRhsField.setValue(JAttribute.manipConstantValue(mDis, mCpInfo));
} else if (attrName.compareTo(JAttribute.SYNTHETIC) == 0) {
JAttribute.manipSynthetic(mDis);
} else if (attrName.compareTo(JAttribute.DEPRECATED) == 0) {
JAttribute.manipDeprecated(mDis);
}
}
/**
* Reads the ATTRIBUTES of the method defined in the Current class.
*
The possible attributes here are
* Deprecated , Synthetic , Code and Exceptions .
*
* @param aRhsMethod Reads attributes into this method.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @see JAttribute
*/
private void readMethodAttributes(JMethod aRhsMethod)
throws IOException {
String attrName = mCpInfo.getUtf8String(mDis.readShort());
if (attrName.compareTo(JAttribute.CODE) == 0) {
JAttribute.manipCode(mDis, aRhsMethod, mCpInfo);
} else if (attrName.compareTo(JAttribute.EXCEPTIONS) == 0) {
// counterpart to 'throws' clause in the source code.
aRhsMethod.setThrowsClasses(JAttribute.manipExceptions(mDis, mCpInfo));
} else if (attrName.compareTo(JAttribute.SYNTHETIC) == 0) {
JAttribute.manipSynthetic(mDis);
} else if (attrName.compareTo(JAttribute.DEPRECATED) == 0) {
JAttribute.manipDeprecated(mDis);
}
}
/**
* Reads the ATTRIBUTES of the whole class itself.
*
The possible attributes here are
* Deprecated , SourceFile.
*
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
* @see JAttribute
*/
private void readClassAttributes()
throws IOException {
int attrIndex = mDis.readShort();
String attrName = mCpInfo.getUtf8String(attrIndex);
if (attrName.compareTo(JAttribute.SOURCEFILE) == 0) {
mInfoClass.setSourceFile(JAttribute.manipSourceFile(mDis, mCpInfo));
} else if (attrName.compareTo(JAttribute.DEPRECATED) == 0) {
JAttribute.manipDeprecated(mDis);
}
}
/**
* Reads an UTF8 entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagUtf8(int aIndex)
throws IOException {
String utfString = mDis.readUTF();
mCpInfo.addUtf8Entry(utfString);
}
/**
* Reads an integer entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagInteger(int aIndex)
throws IOException {
int intValue = mDis.readInt();
mCpInfo.addIntegerEntry(String.valueOf(intValue));
}
/**
* Reads an float entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagFloat(int aIndex)
throws IOException {
float floatValue = mDis.readFloat();
mCpInfo.addFloatEntry(floatValue + "f");
}
/**
* Reads a long entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagLong(int aIndex)
throws IOException {
long longValue = mDis.readLong();
mCpInfo.addLongEntry(longValue + "L");
mCpInfo.addNullEntry();
}
/**
* Reads a double entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagDouble(int aIndex)
throws IOException {
double doubleValue = mDis.readDouble();
mCpInfo.addDoubleEntry(String.valueOf(doubleValue));
mCpInfo.addNullEntry();
}
/**
* Reads an TAG_CLASS entry.
*
u1 tag;
* u2 name_index;
*
* Reads a class entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagClass(int aIndex)
throws IOException {
int classIndex = mDis.readShort();
mCpInfo.addClassEntry(classIndex);
}
/**
* Reads a string entry.
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagString(int aIndex)
throws IOException {
int stringIndex = mDis.readShort();
mCpInfo.addStringEntry(stringIndex);
}
/**
* Reads a TAG_FIELDREF entry.
*
* u1 tag;
* u2 class_index;
* u2 name_and_type_index;
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagFieldRef(int aIndex)
throws IOException {
int classIndex = mDis.readShort();
int nameType = mDis.readShort();
mCpInfo.addFieldRefEntry(classIndex, nameType);
}
/**
* Reads a TAG_METHODREF entry.
*
* u1 tag;
* u2 class_index;
* u2 name_and_type_index;
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagMethodRef(int aIndex)
throws IOException {
int classIndex = mDis.readShort();
int nameType = mDis.readShort();
mCpInfo.addMethodRefEntry(classIndex, nameType);
}
/**
* Reads a TAG_INTERFACEREF entry.
*
* u1 tag;
* u2 class_index;
* u2 name_and_type_index;
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagInterfaceRef(int aIndex)
throws IOException {
int classIndex = mDis.readShort();
int nameType = mDis.readShort();
mCpInfo.addInterfaceRefEntry(classIndex, nameType);
}
/**
* Reads a TAG_NAMETYPE entry.
*
u1 tag;
* u2 name_index;
* u2 descriptor_index;
*
* @param aIndex Index of a ConstantPool Entry.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
private void readTagNameType(int aIndex)
throws IOException {
int nameIndex = mDis.readShort();
int descIndex = mDis.readShort();
mCpInfo.addNameTypeEntry(nameIndex, descIndex);
}
/**
* Parses the given byte array and creates the ClassInfo and
* ConstantPool objects.
*
* @param bytes byte array to be parsed.
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
public void parse(byte[] bytes)
throws IOException, ClassParserException {
parse(new ByteArrayInputStream(bytes), bytes.length, "");
//Path not given.
}
/**
* Parses a class file at the other side of a URL and
* creates the ClassInfo and ConstantPool objects.
*
* @param url a url pointing to a class file to be parsed.
* @throws ClassParserException Thrown if class file not in desired format.
* @throws IOException Thrown if error in stream of bytes containing the
* class file.
*/
public void parse(URL url)
throws IOException, ClassParserException {
parse(url.openConnection().getInputStream(), 1024, // make up a guess
url.getPath());
}
}