/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2010 Eric Lafortune (eric@graphics.cornell.edu) * * 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 proguard.classfile.editor; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; /** * This ClassVisitor fixes references of constant pool entries, fields, * methods, and attributes to classes whose names have changed. Descriptors * of member references are not updated yet. * * @see MemberReferenceFixer * @author Eric Lafortune */ public class ClassReferenceFixer extends SimplifiedVisitor implements ClassVisitor, ConstantVisitor, MemberVisitor, AttributeVisitor, InnerClassesInfoVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor, AnnotationVisitor, ElementValueVisitor { private final boolean ensureUniqueMemberNames; /** * Creates a new ClassReferenceFixer. * @param ensureUniqueMemberNames specifies whether class members whose * descriptor changes should get new, unique * names, in order to avoid naming conflicts * with similar methods. */ public ClassReferenceFixer(boolean ensureUniqueMemberNames) { this.ensureUniqueMemberNames = ensureUniqueMemberNames; } // Implementations for ClassVisitor. public void visitProgramClass(ProgramClass programClass) { // Fix the constant pool. programClass.constantPoolEntriesAccept(this); // Fix class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); // Fix the attributes. programClass.attributesAccept(this); } public void visitLibraryClass(LibraryClass libraryClass) { // Fix class members. libraryClass.fieldsAccept(this); libraryClass.methodsAccept(this); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Has the descriptor changed? String descriptor = programField.getDescriptor(programClass); String newDescriptor = newDescriptor(descriptor, programField.referencedClass); if (!descriptor.equals(newDescriptor)) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the descriptor. programField.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the name, if requested. if (ensureUniqueMemberNames) { String name = programField.getName(programClass); String newName = newUniqueMemberName(name, descriptor); programField.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } } // Fix the attributes. programField.attributesAccept(programClass, this); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Has the descriptor changed? String descriptor = programMethod.getDescriptor(programClass); String newDescriptor = newDescriptor(descriptor, programMethod.referencedClasses); if (!descriptor.equals(newDescriptor)) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the descriptor. programMethod.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the name, if requested. if (ensureUniqueMemberNames) { String name = programMethod.getName(programClass); String newName = newUniqueMemberName(name, descriptor); programMethod.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } } // Fix the attributes. programMethod.attributesAccept(programClass, this); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { // Has the descriptor changed? String descriptor = libraryField.getDescriptor(libraryClass); String newDescriptor = newDescriptor(descriptor, libraryField.referencedClass); // Update the descriptor. libraryField.descriptor = newDescriptor; } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { // Has the descriptor changed? String descriptor = libraryMethod.getDescriptor(libraryClass); String newDescriptor = newDescriptor(descriptor, libraryMethod.referencedClasses); // Update the descriptor. libraryMethod.descriptor = newDescriptor; } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Does the string refer to a class, due to a Class.forName construct? Clazz referencedClass = stringConstant.referencedClass; Member referencedMember = stringConstant.referencedMember; if (referencedClass != null && referencedMember == null) { // Reconstruct the new class name. String externalClassName = stringConstant.getString(clazz); String internalClassName = ClassUtil.internalClassName(externalClassName); String newInternalClassName = newClassName(internalClassName, referencedClass); // Update the String entry if required. if (!newInternalClassName.equals(internalClassName)) { String newExternalClassName = ClassUtil.externalClassName(newInternalClassName); // Refer to a new Utf8 entry. stringConstant.u2stringIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newExternalClassName); } } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Do we know the referenced class? Clazz referencedClass = classConstant.referencedClass; if (referencedClass != null) { // Has the class name changed? String className = classConstant.getName(clazz); String newClassName = newClassName(className, referencedClass); if (!className.equals(newClassName)) { // Refer to a new Utf8 entry. classConstant.u2nameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName); } } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Fix the inner class names. innerClassesAttribute.innerClassEntriesAccept(clazz, this); } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Fix the attributes. codeAttribute.attributesAccept(clazz, method, this); } public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Fix the types of the local variables. localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Fix the signatures of the local variables. localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { // Compute the new signature. String signature = clazz.getString(signatureAttribute.u2signatureIndex); String newSignature = newDescriptor(signature, signatureAttribute.referencedClasses); if (!signature.equals(newSignature)) { signatureAttribute.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); } } public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { // Fix the annotations. annotationsAttribute.annotationsAccept(clazz, this); } public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Fix the annotations. parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); } public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { // Fix the annotation. annotationDefaultAttribute.defaultValueAccept(clazz, this); } // Implementations for InnerClassesInfoVisitor. public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { // Fix the inner class name. int innerClassIndex = innerClassesInfo.u2innerClassIndex; int innerNameIndex = innerClassesInfo.u2innerNameIndex; if (innerClassIndex != 0 && innerNameIndex != 0) { String newInnerName = clazz.getClassName(innerClassIndex); int index = newInnerName.lastIndexOf(ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR); if (index >= 0) { innerClassesInfo.u2innerNameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newInnerName.substring(index + 1)); } } } // Implementations for LocalVariableInfoVisitor. public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Has the descriptor changed? String descriptor = clazz.getString(localVariableInfo.u2descriptorIndex); String newDescriptor = newDescriptor(descriptor, localVariableInfo.referencedClass); if (!descriptor.equals(newDescriptor)) { // Refer to a new Utf8 entry. localVariableInfo.u2descriptorIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); } } // Implementations for LocalVariableTypeInfoVisitor. public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // Has the signature changed? String signature = clazz.getString(localVariableTypeInfo.u2signatureIndex); String newSignature = newDescriptor(signature, localVariableTypeInfo.referencedClasses); if (!signature.equals(newSignature)) { localVariableTypeInfo.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); } } // Implementations for AnnotationVisitor. public void visitAnnotation(Clazz clazz, Annotation annotation) { // Compute the new type name. String typeName = clazz.getString(annotation.u2typeIndex); String newTypeName = newDescriptor(typeName, annotation.referencedClasses); if (!typeName.equals(newTypeName)) { // Refer to a new Utf8 entry. annotation.u2typeIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName); } // Fix the element values. annotation.elementValuesAccept(clazz, this); } // Implementations for ElementValueVisitor. public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { } public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { // Compute the new type name. String typeName = clazz.getString(enumConstantElementValue.u2typeNameIndex); String newTypeName = newDescriptor(typeName, enumConstantElementValue.referencedClasses); if (!typeName.equals(newTypeName)) { // Refer to a new Utf8 entry. enumConstantElementValue.u2typeNameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName); } } public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { // Compute the new class name. String className = clazz.getString(classElementValue.u2classInfoIndex); String newClassName = newDescriptor(className, classElementValue.referencedClasses); if (!className.equals(newClassName)) { // Refer to a new Utf8 entry. classElementValue.u2classInfoIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName); } } public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { // Fix the annotation. annotationElementValue.annotationAccept(clazz, this); } public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { // Fix the element values. arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Small utility methods. private static String newDescriptor(String descriptor, Clazz referencedClass) { // If there is no referenced class, the descriptor won't change. if (referencedClass == null) { return descriptor; } // Unravel and reconstruct the class element of the descriptor. DescriptorClassEnumeration descriptorClassEnumeration = new DescriptorClassEnumeration(descriptor); StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff()); // Only if the descriptor contains a class name (e.g. with an array of // primitive types), the descriptor can change. if (descriptorClassEnumeration.hasMoreClassNames()) { String className = descriptorClassEnumeration.nextClassName(); String fluff = descriptorClassEnumeration.nextFluff(); String newClassName = newClassName(className, referencedClass); newDescriptorBuffer.append(newClassName); newDescriptorBuffer.append(fluff); } return newDescriptorBuffer.toString(); } private static String newDescriptor(String descriptor, Clazz[] referencedClasses) { // If there are no referenced classes, the descriptor won't change. if (referencedClasses == null || referencedClasses.length == 0) { return descriptor; } // Unravel and reconstruct the class elements of the descriptor. DescriptorClassEnumeration descriptorClassEnumeration = new DescriptorClassEnumeration(descriptor); StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff()); int index = 0; while (descriptorClassEnumeration.hasMoreClassNames()) { String className = descriptorClassEnumeration.nextClassName(); boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName(); String fluff = descriptorClassEnumeration.nextFluff(); String newClassName = newClassName(className, referencedClasses[index++]); // Strip the outer class name again, if it's an inner class. if (isInnerClassName) { newClassName = newClassName.substring(newClassName.lastIndexOf(ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR)+1); } newDescriptorBuffer.append(newClassName); newDescriptorBuffer.append(fluff); } return newDescriptorBuffer.toString(); } /** * Returns a unique class member name, based on the given name and descriptor. */ private String newUniqueMemberName(String name, String descriptor) { return name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ? ClassConstants.INTERNAL_METHOD_NAME_INIT : name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); } /** * Returns the new class name based on the given class name and the new * name of the given referenced class. Class names of array types * are handled properly. */ private static String newClassName(String className, Clazz referencedClass) { // If there is no referenced class, the class name won't change. if (referencedClass == null) { return className; } // Reconstruct the class name. String newClassName = referencedClass.getName(); // Is it an array type? if (className.charAt(0) == ClassConstants.INTERNAL_TYPE_ARRAY) { // Add the array prefixes and suffix "[L...;". newClassName = className.substring(0, className.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_START)+1) + newClassName + ClassConstants.INTERNAL_TYPE_CLASS_END; } return newClassName; } }