0001: /* ===========================================================================
0002: * $RCSfile: ClassFile.java,v $
0003: * ===========================================================================
0004: *
0005: * RetroGuard -- an obfuscation package for Java classfiles.
0006: *
0007: * Copyright (c) 1998-2006 Mark Welsh (markw@retrologic.com)
0008: *
0009: * This program can be redistributed and/or modified under the terms of the
0010: * Version 2 of the GNU General Public License as published by the Free
0011: * Software Foundation.
0012: *
0013: * This program is distributed in the hope that it will be useful,
0014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: * GNU General Public License for more details.
0017: *
0018: */
0019:
0020: package COM.rl.obf.classfile;
0021:
0022: import java.io.*;
0023: import java.util.*;
0024: import COM.rl.util.*;
0025:
0026: /**
0027: * This is a representation of the data in a Java class-file (*.class).
0028: * A ClassFile instance representing a *.class file can be generated
0029: * using the static create(DataInput) method, manipulated using various
0030: * operators, and persisted back using the write(DataOutput) method.
0031: *
0032: * @author Mark Welsh
0033: */
0034: public class ClassFile implements ClassConstants {
0035: // Constants -------------------------------------------------------------
0036: public static final String SEP_REGULAR = "/";
0037: public static final String SEP_INNER = "$";
0038: private static final String CLASS_FORNAME_NAME_DESCRIPTOR = "forName(Ljava/lang/String;)Ljava/lang/Class;";
0039: private static final String[] DANGEROUS_CLASS_SIMPLENAME_DESCRIPTOR_ARRAY = {
0040: "getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;",
0041: "getField(Ljava/lang/String;)Ljava/lang/reflect/Field;",
0042: "getDeclaredMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
0043: "getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;" };
0044: private static final String LOG_DANGER_CLASS_PRE = " Your class ";
0045: private static final String LOG_DANGER_CLASS_MID = " calls the java/lang/Class method ";
0046: private static final String LOG_CLASS_FORNAME_MID = " uses '.class' or calls java/lang/Class.";
0047: private static final String[] DANGEROUS_CLASSLOADER_SIMPLENAME_DESCRIPTOR_ARRAY = {
0048: "defineClass(Ljava/lang/String;[BII)Ljava/lang/Class;",
0049: "findLoadedClass(Ljava/lang/String;)Ljava/lang/Class;",
0050: "findSystemClass(Ljava/lang/String;)Ljava/lang/Class;",
0051: "loadClass(Ljava/lang/String;)Ljava/lang/Class;",
0052: "loadClass(Ljava/lang/String;Z)Ljava/lang/Class;" };
0053: private static final String LOG_DANGER_CLASSLOADER_PRE = " Your class ";
0054: private static final String LOG_DANGER_CLASSLOADER_MID = " calls the java/lang/ClassLoader method ";
0055:
0056: // Fields ----------------------------------------------------------------
0057: private int u4magic;
0058: private int u2minorVersion;
0059: private int u2majorVersion;
0060: private ConstantPool constantPool;
0061: private int u2accessFlags;
0062: private int u2this Class;
0063: private int u2super Class;
0064: private int u2interfacesCount;
0065: private int u2interfaces[];
0066: private int u2fieldsCount;
0067: private FieldInfo fields[];
0068: private int u2methodsCount;
0069: private MethodInfo methods[];
0070: private int u2attributesCount;
0071: private AttrInfo attributes[];
0072:
0073: private boolean isUnkAttrGone = false;
0074: private boolean hasReflection = false;
0075: private CpInfo cpIdString = null;
0076:
0077: // Class Methods ---------------------------------------------------------
0078: /**
0079: * Create a new ClassFile from the class file format data in the DataInput
0080: * stream.
0081: *
0082: * @throws IOException if class file is corrupt or incomplete
0083: */
0084: public static ClassFile create(DataInput din) throws Exception {
0085: if (din == null)
0086: throw new IOException("No input stream was provided.");
0087: ClassFile cf = new ClassFile();
0088: cf.read(din);
0089: return cf;
0090: }
0091:
0092: /** Parse a method or field descriptor into a list of parameter names (for methods)
0093: * and a return type, in same format as the Class.forName() method returns . */
0094: public static String[] parseDescriptor(String descriptor)
0095: throws Exception {
0096: return parseDescriptor(descriptor, false, true);
0097: }
0098:
0099: /** Parse a method or field descriptor into a list of parameter names (for methods)
0100: * and a return type, optionally in same format as the Class.forName() method returns . */
0101: public static String[] parseDescriptor(String descriptor,
0102: boolean isDisplay) throws Exception {
0103: return parseDescriptor(descriptor, isDisplay, true);
0104: }
0105:
0106: /** Parse a method or field descriptor into a list of parameter names (for methods)
0107: * and a return type, in same format as the Class.forName() method returns . */
0108: public static String[] parseDescriptor(String descriptor,
0109: boolean isDisplay, boolean doTranslate) throws Exception {
0110: // Check for field descriptor
0111: String[] names = null;
0112: if (descriptor.charAt(0) != '(') {
0113: names = new String[1];
0114: names[0] = descriptor;
0115: } else {
0116: // Method descriptor
0117: Vector namesVec = new Vector();
0118: descriptor = descriptor.substring(1);
0119: String type = "";
0120: while (descriptor.length() > 0) {
0121: switch (descriptor.charAt(0)) {
0122: case '[':
0123: type = type + "[";
0124: descriptor = descriptor.substring(1);
0125: break;
0126:
0127: case 'B':
0128: case 'C':
0129: case 'D':
0130: case 'F':
0131: case 'I':
0132: case 'J':
0133: case 'S':
0134: case 'Z':
0135: case 'V':
0136: namesVec.addElement(type
0137: + descriptor.substring(0, 1));
0138: descriptor = descriptor.substring(1);
0139: type = "";
0140: break;
0141:
0142: case ')':
0143: descriptor = descriptor.substring(1);
0144: break;
0145:
0146: case 'L': {
0147: int pos = descriptor.indexOf(';') + 1;
0148: namesVec.addElement(type
0149: + descriptor.substring(0, pos));
0150: descriptor = descriptor.substring(pos);
0151: type = "";
0152: }
0153: break;
0154:
0155: default:
0156: throw new Exception(
0157: "Illegal field or method descriptor: "
0158: + descriptor);
0159: }
0160: }
0161: names = new String[namesVec.size()];
0162: for (int i = 0; i < names.length; i++) {
0163: names[i] = (String) namesVec.elementAt(i);
0164: }
0165: }
0166:
0167: if (doTranslate) {
0168: // Translate the names from JVM to Class.forName() format.
0169: String[] translatedNames = new String[names.length];
0170: for (int i = 0; i < names.length; i++) {
0171: translatedNames[i] = translateType(names[i], isDisplay);
0172: }
0173: return translatedNames;
0174: } else {
0175: return names;
0176: }
0177: }
0178:
0179: /** Translate a type specifier from the internal JVM convention to the Class.forName() one. */
0180: public static String translateType(String inName, boolean isDisplay)
0181: throws Exception {
0182: String outName = null;
0183: switch (inName.charAt(0)) {
0184: case '[': // For array types, Class.forName() inconsistently uses the internal type name
0185: // but with '/' --> '.'
0186: if (!isDisplay) {
0187: // return the Class.forName() form
0188: outName = translate(inName);
0189: } else {
0190: // return the pretty display form
0191: outName = translateType(inName.substring(1), true)
0192: + "[]";
0193: }
0194: break;
0195:
0196: case 'B':
0197: outName = Byte.TYPE.getName();
0198: break;
0199:
0200: case 'C':
0201: outName = Character.TYPE.getName();
0202: break;
0203:
0204: case 'D':
0205: outName = Double.TYPE.getName();
0206: break;
0207:
0208: case 'F':
0209: outName = Float.TYPE.getName();
0210: break;
0211:
0212: case 'I':
0213: outName = Integer.TYPE.getName();
0214: break;
0215:
0216: case 'J':
0217: outName = Long.TYPE.getName();
0218: break;
0219:
0220: case 'S':
0221: outName = Short.TYPE.getName();
0222: break;
0223:
0224: case 'Z':
0225: outName = Boolean.TYPE.getName();
0226: break;
0227:
0228: case 'V':
0229: outName = Void.TYPE.getName();
0230: break;
0231:
0232: case 'L': {
0233: int pos = inName.indexOf(';');
0234: outName = translate(inName
0235: .substring(1, inName.indexOf(';')));
0236: }
0237: break;
0238:
0239: default:
0240: throw new Exception("Illegal field or method name: "
0241: + inName);
0242: }
0243: return outName;
0244: }
0245:
0246: /** Translate a class name from the internal '/' convention to the regular '.' one. */
0247: public static String translate(String name) throws Exception {
0248: return name.replace('/', '.');
0249: }
0250:
0251: /** Translate a class name from the the regular '.' convention to internal '/' one. */
0252: public static String backTranslate(String name) throws Exception {
0253: return name.replace('.', '/');
0254: }
0255:
0256: /** Is this class in an unsupported version of the file format? */
0257: public boolean hasIncompatibleVersion() {
0258: return (u2majorVersion > MAJOR_VERSION);
0259: }
0260:
0261: /** Return major version of this class's file format. */
0262: public int getMajorVersion() {
0263: return u2majorVersion;
0264: }
0265:
0266: // Instance Methods ------------------------------------------------------
0267: // Private constructor.
0268: private ClassFile() {
0269: }
0270:
0271: // Import the class data to internal representation.
0272: private void read(DataInput din) throws Exception {
0273: // Read the class file
0274: u4magic = din.readInt();
0275: u2minorVersion = din.readUnsignedShort();
0276: u2majorVersion = din.readUnsignedShort();
0277:
0278: // Check this is a valid classfile that we can handle
0279: if (u4magic != MAGIC) {
0280: throw new IOException("Invalid magic number in class file.");
0281: }
0282: //if (u2majorVersion > MAJOR_VERSION)
0283: //{
0284: // throw new IOException("Incompatible version number for class file format.");
0285: //}
0286:
0287: int u2constantPoolCount = din.readUnsignedShort();
0288: CpInfo[] cpInfo = new CpInfo[u2constantPoolCount];
0289: // Fill the constant pool, recalling the zero entry
0290: // is not persisted, nor are the entries following a Long or Double
0291: for (int i = 1; i < u2constantPoolCount; i++) {
0292: cpInfo[i] = CpInfo.create(din);
0293: if ((cpInfo[i] instanceof LongCpInfo)
0294: || (cpInfo[i] instanceof DoubleCpInfo)) {
0295: i++;
0296: }
0297: }
0298: constantPool = new ConstantPool(this , cpInfo);
0299:
0300: u2accessFlags = din.readUnsignedShort();
0301: u2this Class = din.readUnsignedShort();
0302: u2super Class = din.readUnsignedShort();
0303: u2interfacesCount = din.readUnsignedShort();
0304: u2interfaces = new int[u2interfacesCount];
0305: for (int i = 0; i < u2interfacesCount; i++) {
0306: u2interfaces[i] = din.readUnsignedShort();
0307: }
0308: u2fieldsCount = din.readUnsignedShort();
0309: fields = new FieldInfo[u2fieldsCount];
0310: for (int i = 0; i < u2fieldsCount; i++) {
0311: fields[i] = FieldInfo.create(din, this );
0312: }
0313: u2methodsCount = din.readUnsignedShort();
0314: methods = new MethodInfo[u2methodsCount];
0315: for (int i = 0; i < u2methodsCount; i++) {
0316: methods[i] = MethodInfo.create(din, this );
0317: }
0318: u2attributesCount = din.readUnsignedShort();
0319: attributes = new AttrInfo[u2attributesCount];
0320: for (int i = 0; i < u2attributesCount; i++) {
0321: attributes[i] = AttrInfo.create(din, this );
0322: }
0323: checkReflection();
0324: }
0325:
0326: /**
0327: * Define a constant String to include in this output class file.
0328: */
0329: public void setIdString(String id) throws Exception {
0330: if (id != null) {
0331: cpIdString = new Utf8CpInfo(id);
0332: } else {
0333: cpIdString = null;
0334: }
0335: }
0336:
0337: // Check for reflection methods and set flag
0338: private boolean checkReflection() throws Exception {
0339: // Need only check CONSTANT_Methodref entries of constant pool since
0340: // methods belong to classes 'Class' and 'ClassLoader', not interfaces.
0341: for (Enumeration enm = constantPool.elements(); enm
0342: .hasMoreElements();) {
0343: Object o = enm.nextElement();
0344: if (o instanceof MethodrefCpInfo) {
0345: // Get the method class name, simple name and descriptor
0346: MethodrefCpInfo entry = (MethodrefCpInfo) o;
0347: ClassCpInfo classEntry = (ClassCpInfo) getCpEntry(entry
0348: .getClassIndex());
0349: String className = ((Utf8CpInfo) getCpEntry(classEntry
0350: .getNameIndex())).getString();
0351: NameAndTypeCpInfo ntEntry = (NameAndTypeCpInfo) getCpEntry(entry
0352: .getNameAndTypeIndex());
0353: String name = ((Utf8CpInfo) getCpEntry(ntEntry
0354: .getNameIndex())).getString();
0355: String descriptor = ((Utf8CpInfo) getCpEntry(ntEntry
0356: .getDescriptorIndex())).getString();
0357:
0358: // Check if this is Class.forName
0359: if (className.equals("java/lang/Class")
0360: && CLASS_FORNAME_NAME_DESCRIPTOR.equals(name
0361: + descriptor)) {
0362: hasReflection = true;
0363: }
0364: }
0365: }
0366: return hasReflection;
0367: }
0368:
0369: /** Return the access modifiers for this classfile. */
0370: public int getModifiers() throws Exception {
0371: return u2accessFlags;
0372: }
0373:
0374: /** Return the name of this classfile. */
0375: public String getName() throws Exception {
0376: return toName(u2this Class);
0377: }
0378:
0379: /** Return the name of this class's superclass. */
0380: public String getSuper() throws Exception {
0381: // This may be java/lang/Object, in which case there is no super
0382: return (u2super Class == 0) ? null : toName(u2super Class);
0383: }
0384:
0385: /** Return the names of this class's interfaces. */
0386: public String[] getInterfaces() throws Exception {
0387: String[] interfaces = new String[u2interfacesCount];
0388: for (int i = 0; i < u2interfacesCount; i++) {
0389: interfaces[i] = toName(u2interfaces[i]);
0390: }
0391: return interfaces;
0392: }
0393:
0394: // Convert a CP index to a class name.
0395: private String toName(int u2index) throws Exception {
0396: CpInfo classEntry = getCpEntry(u2index);
0397: if (classEntry instanceof ClassCpInfo) {
0398: CpInfo nameEntry = getCpEntry(((ClassCpInfo) classEntry)
0399: .getNameIndex());
0400: if (nameEntry instanceof Utf8CpInfo) {
0401: return ((Utf8CpInfo) nameEntry).getString();
0402: } else {
0403: throw new Exception(
0404: "Inconsistent Constant Pool in class file.");
0405: }
0406: } else {
0407: throw new Exception(
0408: "Inconsistent Constant Pool in class file.");
0409: }
0410: }
0411:
0412: /** Return number of methods in class. */
0413: public int getMethodCount() throws Exception {
0414: return methods.length;
0415: }
0416:
0417: /** Return i'th method in class. */
0418: public MethodInfo getMethod(int i) throws Exception {
0419: return methods[i];
0420: }
0421:
0422: /** Remove i'th method from class. */
0423: public void removeMethod(int i) throws Exception {
0424: // Trim the method
0425: MethodInfo newMethods[] = new MethodInfo[methods.length - 1];
0426: if (i > 0) {
0427: System.arraycopy(methods, 0, newMethods, 0, i);
0428: }
0429: if (i < methods.length - 1) {
0430: System.arraycopy(methods, i + 1, newMethods, i,
0431: methods.length - i - 1);
0432: }
0433: methods = newMethods;
0434: --u2methodsCount;
0435: }
0436:
0437: /** Return number of fields in class. */
0438: public int getFieldCount() throws Exception {
0439: return fields.length;
0440: }
0441:
0442: /** Return i'th field in class. */
0443: public FieldInfo getField(int i) throws Exception {
0444: return fields[i];
0445: }
0446:
0447: /** Remove i'th field from class. */
0448: public void removeField(int i) throws Exception {
0449: // Trim the field
0450: FieldInfo newFields[] = new FieldInfo[fields.length - 1];
0451: if (i > 0) {
0452: System.arraycopy(fields, 0, newFields, 0, i);
0453: }
0454: if (i < fields.length - 1) {
0455: System.arraycopy(fields, i + 1, newFields, i, fields.length
0456: - i - 1);
0457: }
0458: fields = newFields;
0459: --u2fieldsCount;
0460: }
0461:
0462: /** Lookup the entry in the constant pool and return as an Object. */
0463: protected CpInfo getCpEntry(int cpIndex) throws Exception {
0464: return constantPool.getCpEntry(cpIndex);
0465: }
0466:
0467: /** Remap a specified Utf8 entry to the given value and return its new index. */
0468: public int remapUtf8To(String newString, int oldIndex)
0469: throws Exception {
0470: return constantPool.remapUtf8To(newString, oldIndex);
0471: }
0472:
0473: /** Lookup the UTF8 string in the constant pool. Used in debugging. */
0474: protected String getUtf8(int cpIndex) throws Exception {
0475: CpInfo i = getCpEntry(cpIndex);
0476: if (i instanceof Utf8CpInfo) {
0477: return ((Utf8CpInfo) i).getString();
0478: } else {
0479: return "[not UTF8]";
0480: }
0481: }
0482:
0483: /** Does this class contain reflection methods? */
0484: public boolean hasReflection() {
0485: return hasReflection;
0486: }
0487:
0488: /** List methods which can break obfuscated code, and log to a String[]. */
0489: public String[] getDangerousMethods() throws Exception {
0490: Vector list = new Vector();
0491: list = listDangerMethods(list);
0492: // Copy any warnings to a String[]
0493: String[] warnings = new String[list.size()];
0494: for (int i = 0; i < warnings.length; i++) {
0495: warnings[i] = (String) list.elementAt(i);
0496: }
0497: return warnings;
0498: }
0499:
0500: /** List methods which can break obfuscated code, and log to a Vector. */
0501: public Vector listDangerMethods(Vector list) throws Exception {
0502: // Need only check CONSTANT_Methodref entries of constant pool since
0503: // dangerous methods belong to classes 'Class' and 'ClassLoader', not to interfaces.
0504: for (Enumeration enm = constantPool.elements(); enm
0505: .hasMoreElements();) {
0506: Object o = enm.nextElement();
0507: if (o instanceof MethodrefCpInfo) {
0508: // Get the method class name, simple name and descriptor
0509: MethodrefCpInfo entry = (MethodrefCpInfo) o;
0510: ClassCpInfo classEntry = (ClassCpInfo) getCpEntry(entry
0511: .getClassIndex());
0512: String className = ((Utf8CpInfo) getCpEntry(classEntry
0513: .getNameIndex())).getString();
0514: NameAndTypeCpInfo ntEntry = (NameAndTypeCpInfo) getCpEntry(entry
0515: .getNameAndTypeIndex());
0516: String name = ((Utf8CpInfo) getCpEntry(ntEntry
0517: .getNameIndex())).getString();
0518: String descriptor = ((Utf8CpInfo) getCpEntry(ntEntry
0519: .getDescriptorIndex())).getString();
0520:
0521: // Check if this is on the proscribed list
0522: if (className.equals("java/lang/Class")) {
0523: if (CLASS_FORNAME_NAME_DESCRIPTOR.equals(name
0524: + descriptor)) {
0525: list.addElement(LOG_DANGER_CLASS_PRE
0526: + getName() + LOG_CLASS_FORNAME_MID
0527: + CLASS_FORNAME_NAME_DESCRIPTOR);
0528: } else if (Tools
0529: .isInArray(name + descriptor,
0530: DANGEROUS_CLASS_SIMPLENAME_DESCRIPTOR_ARRAY)) {
0531: list.addElement(LOG_DANGER_CLASS_PRE
0532: + getName() + LOG_DANGER_CLASS_MID
0533: + name + descriptor);
0534: }
0535: } else if (Tools
0536: .isInArray(name + descriptor,
0537: DANGEROUS_CLASSLOADER_SIMPLENAME_DESCRIPTOR_ARRAY)) {
0538: list.addElement(LOG_DANGER_CLASSLOADER_PRE
0539: + getName() + LOG_DANGER_CLASSLOADER_MID
0540: + name + descriptor);
0541: }
0542: }
0543: }
0544: return list;
0545: }
0546:
0547: /** Check for direct references to Utf8 constant pool entries. */
0548: public void markUtf8Refs(ConstantPool pool) throws Exception {
0549: try {
0550: // Check for references to Utf8 from outside the constant pool
0551: for (int i = 0; i < fields.length; i++) {
0552: fields[i].markUtf8Refs(pool);
0553: }
0554: for (int i = 0; i < methods.length; i++) {
0555: methods[i].markUtf8Refs(pool); // also checks Code/LVT attrs here
0556: }
0557: for (int i = 0; i < attributes.length; i++) {
0558: attributes[i].markUtf8Refs(pool); // checks InnerClasses, SourceFile and all attr names
0559: }
0560:
0561: // Now check for references from other CP entries
0562: for (Enumeration enm = pool.elements(); enm
0563: .hasMoreElements();) {
0564: Object o = enm.nextElement();
0565: if (o instanceof NameAndTypeCpInfo
0566: || o instanceof ClassCpInfo
0567: || o instanceof StringCpInfo) {
0568: ((CpInfo) o).markUtf8Refs(pool);
0569: }
0570: }
0571: } catch (ArrayIndexOutOfBoundsException e) {
0572: throw new Exception(
0573: "Inconsistent reference to constant pool.");
0574: }
0575: }
0576:
0577: /** Check for direct references to NameAndType constant pool entries. */
0578: public void markNTRefs(ConstantPool pool) throws Exception {
0579: try {
0580: // Now check the method and field CP entries
0581: for (Enumeration enm = pool.elements(); enm
0582: .hasMoreElements();) {
0583: Object o = enm.nextElement();
0584: if (o instanceof RefCpInfo) {
0585: ((CpInfo) o).markNTRefs(pool);
0586: }
0587: }
0588: } catch (ArrayIndexOutOfBoundsException e) {
0589: throw new Exception(
0590: "Inconsistent reference to constant pool.");
0591: }
0592: }
0593:
0594: /**
0595: * Trim attributes from the classfile ('Code', 'Exceptions', 'ConstantValue'
0596: * are preserved, all others except the list in the String[] are killed).
0597: */
0598: public void trimAttrsExcept(String[] extraAttrs) throws Exception {
0599: // Merge additional attributes with required list
0600: String[] keepAttrs = REQUIRED_ATTRS;
0601: if (extraAttrs != null && extraAttrs.length > 0) {
0602: String[] tmp = new String[keepAttrs.length
0603: + extraAttrs.length];
0604: System.arraycopy(keepAttrs, 0, tmp, 0, keepAttrs.length);
0605: System.arraycopy(extraAttrs, 0, tmp, keepAttrs.length,
0606: extraAttrs.length);
0607: keepAttrs = tmp;
0608: }
0609:
0610: // Traverse all attributes, removing all except those on 'keep' list
0611: for (int i = 0; i < fields.length; i++) {
0612: fields[i].trimAttrsExcept(keepAttrs);
0613: }
0614: for (int i = 0; i < methods.length; i++) {
0615: methods[i].trimAttrsExcept(keepAttrs);
0616: }
0617: for (int i = 0; i < attributes.length; i++) {
0618: if (Tools.isInArray(attributes[i].getAttrName(), keepAttrs)) {
0619: attributes[i].trimAttrsExcept(keepAttrs);
0620: } else {
0621: attributes[i] = null;
0622: }
0623: }
0624:
0625: // Delete the marked attributes
0626: AttrInfo[] left = new AttrInfo[attributes.length];
0627: int j = 0;
0628: for (int i = 0; i < attributes.length; i++) {
0629: if (attributes[i] != null) {
0630: left[j++] = attributes[i];
0631: }
0632: }
0633: attributes = new AttrInfo[j];
0634: System.arraycopy(left, 0, attributes, 0, j);
0635: u2attributesCount = j;
0636:
0637: // Signal that unknown attributes are gone
0638: isUnkAttrGone = true;
0639: }
0640:
0641: /** Update the constant pool reference counts. */
0642: public void updateRefCount() throws Exception {
0643: constantPool.updateRefCount();
0644: }
0645:
0646: /**
0647: * Trim attributes from the classfile ('Code', 'Exceptions', 'ConstantValue'
0648: * are preserved, all others are killed).
0649: */
0650: public void trimAttrs() throws Exception {
0651: trimAttrsExcept(null);
0652: }
0653:
0654: /**
0655: * Remap SourceFile attribute to constant string "SourceFile"
0656: */
0657: public void setDummySourceFile() throws Exception {
0658: for (int i = 0; i < attributes.length; i++) {
0659: if (ATTR_SourceFile.equals(attributes[i].getAttrName())) {
0660: ((SourceFileAttrInfo) attributes[i])
0661: .setAsDummy(constantPool);
0662: }
0663: }
0664: }
0665:
0666: /** Remove unnecessary attributes from the class. */
0667: public void trimAttrs(NameMapper nm) throws Exception {
0668: String[] attrs = nm.getAttrsToKeep();
0669: if (attrs.length > 0) {
0670: trimAttrsExcept(attrs);
0671: } else {
0672: trimAttrs();
0673: }
0674: }
0675:
0676: /** Remap the entities in the specified ClassFile. */
0677: public void remap(NameMapper nm, PrintWriter log,
0678: boolean enableMapClassString, boolean enableDummySourceFile)
0679: throws Exception {
0680: // If requested by '.option LineNumberDebug' make SourceFile attribute
0681: // into dummy constant string "SourceFile"
0682: if (enableDummySourceFile) {
0683: setDummySourceFile();
0684: }
0685:
0686: // Go through all of class's fields and methods mapping 'name' and 'descriptor' references
0687: String this ClassName = ((Utf8CpInfo) getCpEntry(((ClassCpInfo) getCpEntry(u2this Class))
0688: .getNameIndex())).getString();
0689: for (int i = 0; i < u2fieldsCount; i++) {
0690: // Remap field 'name', unless it is 'Synthetic'
0691: FieldInfo field = fields[i];
0692: if (!field.isSynthetic()) {
0693: Utf8CpInfo nameUtf = (Utf8CpInfo) getCpEntry(field
0694: .getNameIndex());
0695: String remapName = nm.mapField(this ClassName, nameUtf
0696: .getString());
0697: field.setNameIndex(constantPool.remapUtf8To(remapName,
0698: field.getNameIndex()));
0699: }
0700:
0701: // Remap field 'descriptor'
0702: Utf8CpInfo descUtf = (Utf8CpInfo) getCpEntry(field
0703: .getDescriptorIndex());
0704: String remapDesc = nm.mapDescriptor(descUtf.getString());
0705: field.setDescriptorIndex(constantPool.remapUtf8To(
0706: remapDesc, field.getDescriptorIndex()));
0707: }
0708: for (int i = 0; i < u2methodsCount; i++) {
0709: // Remap method 'name', unless it is 'Synthetic'
0710: MethodInfo method = methods[i];
0711: Utf8CpInfo descUtf = (Utf8CpInfo) getCpEntry(method
0712: .getDescriptorIndex());
0713: if (!method.isSynthetic()) {
0714: Utf8CpInfo nameUtf = (Utf8CpInfo) getCpEntry(method
0715: .getNameIndex());
0716: String remapName = nm.mapMethod(this ClassName, nameUtf
0717: .getString(), descUtf.getString());
0718: method.setNameIndex(constantPool.remapUtf8To(remapName,
0719: method.getNameIndex()));
0720: }
0721:
0722: // Remap method 'descriptor'
0723: String remapDesc = nm.mapDescriptor(descUtf.getString());
0724: method.setDescriptorIndex(constantPool.remapUtf8To(
0725: remapDesc, method.getDescriptorIndex()));
0726: }
0727:
0728: // Remap all field/method names and descriptors in the constant pool (depends on class names)
0729: int currentCpLength = constantPool.length(); // constant pool can be extended (never contracted) during loop
0730: for (int i = 0; i < currentCpLength; i++) {
0731: CpInfo cpInfo = getCpEntry(i);
0732: if (cpInfo != null) {
0733: // If this is a CONSTANT_Fieldref, CONSTANT_Methodref or CONSTANT_InterfaceMethodref
0734: // get the CONSTANT_NameAndType and remap the name and the components of the
0735: // descriptor string.
0736: if (cpInfo instanceof RefCpInfo) {
0737: // Get the unmodified class name
0738: ClassCpInfo classInfo = (ClassCpInfo) getCpEntry(((RefCpInfo) cpInfo)
0739: .getClassIndex());
0740: Utf8CpInfo classUtf = (Utf8CpInfo) getCpEntry(classInfo
0741: .getNameIndex());
0742: String className = classUtf.getString();
0743:
0744: // Get the current N&T reference and its 'name' and 'descriptor' utf's
0745: int ntIndex = ((RefCpInfo) cpInfo)
0746: .getNameAndTypeIndex();
0747: NameAndTypeCpInfo nameTypeInfo = (NameAndTypeCpInfo) getCpEntry(ntIndex);
0748: Utf8CpInfo refUtf = (Utf8CpInfo) getCpEntry(nameTypeInfo
0749: .getNameIndex());
0750: Utf8CpInfo descUtf = (Utf8CpInfo) getCpEntry(nameTypeInfo
0751: .getDescriptorIndex());
0752:
0753: // Get the remapped versions of 'name' and 'descriptor'
0754: String remapRef;
0755: if (cpInfo instanceof FieldrefCpInfo) {
0756: remapRef = nm.mapField(className, refUtf
0757: .getString());
0758: } else {
0759: remapRef = nm.mapMethod(className, refUtf
0760: .getString(), descUtf.getString());
0761: }
0762: String remapDesc = nm.mapDescriptor(descUtf
0763: .getString());
0764:
0765: // If a remap is required, make a new N&T (increment ref count on 'name' and
0766: // 'descriptor', decrement original N&T's ref count, set new N&T ref count to 1),
0767: // remap new N&T's utf's
0768: if (!remapRef.equals(refUtf.getString())
0769: || !remapDesc.equals(descUtf.getString())) {
0770: // Get the new N&T guy
0771: NameAndTypeCpInfo newNameTypeInfo;
0772: if (nameTypeInfo.getRefCount() == 1) {
0773: newNameTypeInfo = nameTypeInfo;
0774: } else {
0775: // Create the new N&T info
0776: newNameTypeInfo = (NameAndTypeCpInfo) nameTypeInfo
0777: .clone();
0778:
0779: // Adjust its reference counts of its utf's
0780: ((CpInfo) getCpEntry(newNameTypeInfo
0781: .getNameIndex())).incRefCount();
0782: ((CpInfo) getCpEntry(newNameTypeInfo
0783: .getDescriptorIndex()))
0784: .incRefCount();
0785:
0786: // Append it to the Constant Pool, and
0787: // point the RefCpInfo entry to the new N&T data
0788: ((RefCpInfo) cpInfo)
0789: .setNameAndTypeIndex(constantPool
0790: .addEntry(newNameTypeInfo));
0791:
0792: // Adjust reference counts from RefCpInfo
0793: newNameTypeInfo.incRefCount();
0794: nameTypeInfo.decRefCount();
0795: }
0796:
0797: // Remap the 'name' and 'descriptor' utf's in N&T
0798: newNameTypeInfo.setNameIndex(constantPool
0799: .remapUtf8To(remapRef, newNameTypeInfo
0800: .getNameIndex()));
0801: newNameTypeInfo.setDescriptorIndex(constantPool
0802: .remapUtf8To(remapDesc, newNameTypeInfo
0803: .getDescriptorIndex()));
0804: }
0805: }
0806: }
0807: }
0808:
0809: // Remap all class references to Utf
0810: for (int i = 0; i < constantPool.length(); i++) {
0811: CpInfo cpInfo = getCpEntry(i);
0812: if (cpInfo != null) {
0813: // If this is CONSTANT_Class, remap the class-name Utf8 entry
0814: if (cpInfo instanceof ClassCpInfo) {
0815: ClassCpInfo classInfo = (ClassCpInfo) cpInfo;
0816: Utf8CpInfo utf = (Utf8CpInfo) getCpEntry(classInfo
0817: .getNameIndex());
0818: String remapClass = nm.mapClass(utf.getString());
0819: int remapIndex = constantPool.remapUtf8To(
0820: remapClass, classInfo.getNameIndex());
0821: classInfo.setNameIndex(remapIndex);
0822: }
0823: }
0824: }
0825:
0826: // Remap all annotation type references to Utf8 classes
0827: for (int j = 0; j < u2attributesCount; j++) {
0828: attributes[j].remap(this , nm);
0829: }
0830: for (int i = 0; i < u2methodsCount; i++) {
0831: for (int j = 0; j < methods[i].u2attributesCount; j++) {
0832: methods[i].attributes[j].remap(this , nm);
0833: }
0834: }
0835: for (int i = 0; i < u2fieldsCount; i++) {
0836: for (int j = 0; j < fields[i].u2attributesCount; j++) {
0837: fields[i].attributes[j].remap(this , nm);
0838: }
0839: }
0840:
0841: // If reflection, attempt to remap all class string references
0842: // NOTE - hasReflection wasn't picking up reflection in inner classes
0843: // because they call to the outer class to do forName(...).
0844: // Therefore removed.
0845: //if (hasReflection && enableMapClassString)
0846: if (/* hasReflection && */enableMapClassString) {
0847: remapClassStrings(nm, log);
0848: }
0849: }
0850:
0851: // Remap Class.forName and .class, leaving other identical Strings alone
0852: private void remapClassStrings(NameMapper nm, PrintWriter log)
0853: throws Exception {
0854: // Visit all method Code attributes, collecting information on remap
0855: FlagHashtable cpToFlag = new FlagHashtable();
0856: for (int i = 0; i < methods.length; i++) {
0857: MethodInfo methodInfo = methods[i];
0858: for (int j = 0; j < methodInfo.attributes.length; j++) {
0859: AttrInfo attrInfo = methodInfo.attributes[j];
0860: if (attrInfo instanceof CodeAttrInfo) {
0861: cpToFlag = ((CodeAttrInfo) attrInfo)
0862: .walkFindClassStrings(cpToFlag);
0863: }
0864: }
0865: }
0866: // Analyse String mapping flags and generate updated Strings
0867: Hashtable cpUpdate = new Hashtable();
0868: for (Enumeration enm = cpToFlag.keys(); enm.hasMoreElements();) {
0869: StringCpInfo stringCpInfo = (StringCpInfo) enm
0870: .nextElement();
0871: StringCpInfoFlags flags = (StringCpInfoFlags) cpToFlag
0872: .get(stringCpInfo);
0873: String name = backTranslate(((Utf8CpInfo) getCpEntry(stringCpInfo
0874: .getStringIndex())).getString());
0875: // String accessed as Class.forName or .class?
0876: if (isClassSpec(name) && flags.forNameFlag) {
0877: String remapName = nm.mapClass(name);
0878: if (!remapName.equals(name)) // skip if no remap needed
0879: {
0880: boolean simpleRemap = false;
0881: // String accessed in another way, so split in ConstantPool
0882: if (flags.otherFlag) {
0883: // Create a new String/Utf8 for remapped Class-name
0884: int remapUtf8Index = constantPool
0885: .addUtf8Entry(translate(remapName));
0886: StringCpInfo remapStringInfo = new StringCpInfo();
0887: remapStringInfo.setStringIndex(remapUtf8Index);
0888: int remapStringIndex = constantPool
0889: .addEntry(remapStringInfo);
0890: // Default to full remap if new String would require
0891: // ldc_w to access - we can't cope with that yet
0892: if (remapStringIndex > 0xFF) {
0893: simpleRemap = true;
0894: log
0895: .println("# WARNING MapClassString: non-.class/Class.forName() string remapped");
0896: } else {
0897: log
0898: .println("# MapClassString (partial) in class "
0899: + getName()
0900: + ": "
0901: + name
0902: + " -> " + remapName);
0903: // Add to cpUpdate hash for later remap in Code
0904: cpUpdate.put(
0905: new Integer(flags.stringIndex),
0906: new Integer(remapStringIndex));
0907: }
0908: } else // String only accessed as Class.forName
0909: {
0910: simpleRemap = true;
0911: }
0912: if (simpleRemap) {
0913: log.println("# MapClassString (full) in class "
0914: + getName() + ": " + name + " -> "
0915: + remapName);
0916: // Just remap the existing String/Utf8, since it is
0917: // only used for Class.forName or .class, or maybe
0918: // ldc_w was needed (which gives improper String remap)
0919: int remapIndex = constantPool.remapUtf8To(
0920: translate(remapName), stringCpInfo
0921: .getStringIndex());
0922: stringCpInfo.setStringIndex(remapIndex);
0923: }
0924: }
0925: }
0926: }
0927: // Visit all method Code attributes, remapping .class/Class.forName
0928: for (int i = 0; i < methods.length; i++) {
0929: MethodInfo methodInfo = methods[i];
0930: for (int j = 0; j < methodInfo.attributes.length; j++) {
0931: AttrInfo attrInfo = methodInfo.attributes[j];
0932: if (attrInfo instanceof CodeAttrInfo) {
0933: ((CodeAttrInfo) attrInfo)
0934: .walkUpdateClassStrings(cpUpdate);
0935: }
0936: }
0937: }
0938: }
0939:
0940: // Is this String a valid class specifier?
0941: private static boolean isClassSpec(String s) //throws Exception
0942: {
0943: if (s.length() == 0)
0944: return false;
0945: int pos = -1;
0946: while ((pos = s.lastIndexOf('/')) != -1) {
0947: if (!isJavaIdentifier(s.substring(pos + 1)))
0948: return false;
0949: s = s.substring(0, pos);
0950: }
0951: if (!isJavaIdentifier(s))
0952: return false;
0953: return true;
0954: }
0955:
0956: // Is this String a valid Java identifier?
0957: private static boolean isJavaIdentifier(String s) // throws Exception
0958: {
0959: if (s.length() == 0
0960: || !Character.isJavaIdentifierStart(s.charAt(0)))
0961: return false;
0962: for (int i = 1; i < s.length(); i++)
0963: if (!Character.isJavaIdentifierPart(s.charAt(i)))
0964: return false;
0965: return true;
0966: }
0967:
0968: /** Export the representation to a DataOutput stream. */
0969: public void write(DataOutput dout) throws Exception {
0970: if (dout == null)
0971: throw new IOException("No output stream was provided.");
0972: dout.writeInt(u4magic);
0973: dout.writeShort(u2minorVersion);
0974: dout.writeShort(u2majorVersion);
0975: dout.writeShort(constantPool.length()
0976: + (cpIdString != null ? 1 : 0));
0977: for (Enumeration enm = constantPool.elements(); enm
0978: .hasMoreElements();) {
0979: CpInfo cpInfo = (CpInfo) enm.nextElement();
0980: if (cpInfo != null) {
0981: cpInfo.write(dout);
0982: }
0983: }
0984: if (cpIdString != null) {
0985: cpIdString.write(dout);
0986: }
0987: dout.writeShort(u2accessFlags);
0988: dout.writeShort(u2this Class);
0989: dout.writeShort(u2super Class);
0990: dout.writeShort(u2interfacesCount);
0991: for (int i = 0; i < u2interfacesCount; i++) {
0992: dout.writeShort(u2interfaces[i]);
0993: }
0994: dout.writeShort(u2fieldsCount);
0995: for (int i = 0; i < u2fieldsCount; i++) {
0996: fields[i].write(dout);
0997: }
0998: dout.writeShort(u2methodsCount);
0999: for (int i = 0; i < u2methodsCount; i++) {
1000: methods[i].write(dout);
1001: }
1002: dout.writeShort(u2attributesCount);
1003: for (int i = 0; i < u2attributesCount; i++) {
1004: attributes[i].write(dout);
1005: }
1006: }
1007:
1008: /** Dump the content of the class file to the specified file (used for debugging). */
1009: public void dump(PrintWriter pw) throws Exception {
1010: pw
1011: .println("_____________________________________________________________________");
1012: pw.println("CLASS: " + getName());
1013: pw.println("Magic: " + Integer.toHexString(u4magic));
1014: pw.println("Minor version: "
1015: + Integer.toHexString(u2minorVersion));
1016: pw.println("Major version: "
1017: + Integer.toHexString(u2majorVersion));
1018: pw.println();
1019: pw.println("CP length: "
1020: + Integer.toHexString(constantPool.length()));
1021: for (int i = 0; i < constantPool.length(); i++) {
1022: CpInfo cpInfo = (CpInfo) constantPool.getCpEntry(i);
1023: if (cpInfo != null) {
1024: cpInfo.dump(pw, this , i);
1025: }
1026: }
1027: pw.println("Access: " + Integer.toHexString(u2accessFlags));
1028: pw.println("This class: " + getName());
1029: pw.println("Superclass: " + getSuper());
1030: pw.println("Interfaces count: "
1031: + Integer.toHexString(u2interfacesCount));
1032: for (int i = 0; i < u2interfacesCount; i++) {
1033: CpInfo info = getCpEntry(u2interfaces[i]);
1034: if (info == null) {
1035: pw.println(" Interface " + Integer.toHexString(i)
1036: + ": (null)");
1037: } else {
1038: pw.println(" Interface "
1039: + Integer.toHexString(i)
1040: + ": "
1041: + ((Utf8CpInfo) getCpEntry(((ClassCpInfo) info)
1042: .getNameIndex())).getString());
1043: }
1044: }
1045: pw.println("Fields count: "
1046: + Integer.toHexString(u2fieldsCount));
1047: for (int i = 0; i < u2fieldsCount; i++) {
1048: ClassItemInfo info = fields[i];
1049: if (info == null) {
1050: pw.println(" Field " + Integer.toHexString(i)
1051: + ": (null)");
1052: } else {
1053: pw
1054: .println(" Field "
1055: + Integer.toHexString(i)
1056: + ": "
1057: + ((Utf8CpInfo) getCpEntry(info
1058: .getNameIndex())).getString()
1059: + " "
1060: + ((Utf8CpInfo) getCpEntry(info
1061: .getDescriptorIndex()))
1062: .getString());
1063: }
1064: //pw.println(" Attrs count: " + Integer.toHexString(info.u2attributesCount));
1065: //for (int j = 0; j < info.u2attributesCount; j++)
1066: //{
1067: // info.attributes[j].dump(pw, this);
1068: //}
1069: }
1070: pw.println("Methods count: "
1071: + Integer.toHexString(u2methodsCount));
1072: for (int i = 0; i < u2methodsCount; i++) {
1073: ClassItemInfo info = methods[i];
1074: if (info == null) {
1075: pw.println(" Method " + Integer.toHexString(i)
1076: + ": (null)");
1077: } else {
1078: pw
1079: .println(" Method "
1080: + Integer.toHexString(i)
1081: + ": "
1082: + ((Utf8CpInfo) getCpEntry(info
1083: .getNameIndex())).getString()
1084: + " "
1085: + ((Utf8CpInfo) getCpEntry(info
1086: .getDescriptorIndex()))
1087: .getString()
1088: + " "
1089: + Integer.toHexString(info
1090: .getAccessFlags()));
1091: }
1092: //pw.println(" Attrs count: " + Integer.toHexString(info.u2attributesCount));
1093: //for (int j = 0; j < info.u2attributesCount; j++)
1094: //{
1095: // info.attributes[j].dump(pw, this);
1096: //}
1097: }
1098: //pw.println("Attrs count: " + Integer.toHexString(u2attributesCount));
1099: //for (int i = 0; i < u2attributesCount; i++)
1100: //{
1101: // attributes[i].dump(pw, this);
1102: //}
1103: }
1104: }
|