blob: 98579033395846c799021211fc0f7ba61fd3cc37 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2009 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;
}
}