/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2013 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.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; /** * This ClassVisitor fixes constant pool field and method references to fields * and methods whose names or descriptors have changed. * * @author Eric Lafortune */ public class MemberReferenceFixer extends SimplifiedVisitor implements ClassVisitor, ConstantVisitor, MemberVisitor, AttributeVisitor, AnnotationVisitor, ElementValueVisitor { private static final boolean DEBUG = false; private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater(); // Parameter for the visitor methods. private int constantIndex; // Return values for the visitor methods. private boolean isInterfaceMethod; private boolean stackSizesMayHaveChanged; // Implementations for ClassVisitor. public void visitProgramClass(ProgramClass programClass) { stackSizesMayHaveChanged = false; // Fix the constant pool entries. for (int index = 1; index < programClass.u2constantPoolCount; index++) { Constant constant = programClass.constantPool[index]; if (constant != null) { // Fix the entry, replacing it entirely if needed. this.constantIndex = index; constant.accept(programClass, this); } } // Fix the class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); // Fix the attributes. programClass.attributesAccept(this); } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Does the string refer to a class member, due to a // Class.get[Declared]{Field,Method} construct? Member referencedMember = stringConstant.referencedMember; if (referencedMember != null) { Clazz referencedClass = stringConstant.referencedClass; // Does it have a new name? String newName = referencedMember.getName(referencedClass); if (!stringConstant.getString(clazz).equals(newName)) { if (DEBUG) { debug(clazz, stringConstant, referencedClass, referencedMember); } // Update the name. stringConstant.u2stringIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newName); } } } public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { // Do we know the referenced field? Member referencedMember = fieldrefConstant.referencedMember; if (referencedMember != null) { Clazz referencedClass = fieldrefConstant.referencedClass; // Does it have a new name or type? String newName = referencedMember.getName(referencedClass); String newType = referencedMember.getDescriptor(referencedClass); if (!fieldrefConstant.getName(clazz).equals(newName) || !fieldrefConstant.getType(clazz).equals(newType)) { if (DEBUG) { debug(clazz, fieldrefConstant, referencedClass, referencedMember); } // Update the name and type index. fieldrefConstant.u2nameAndTypeIndex = new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType); } } } public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { // Do we know the referenced interface method? Member referencedMember = interfaceMethodrefConstant.referencedMember; if (referencedMember != null) { Clazz referencedClass = interfaceMethodrefConstant.referencedClass; // Does it have a new name or type? String newName = referencedMember.getName(referencedClass); String newType = referencedMember.getDescriptor(referencedClass); if (!interfaceMethodrefConstant.getName(clazz).equals(newName) || !interfaceMethodrefConstant.getType(clazz).equals(newType)) { if (DEBUG) { debug(clazz, interfaceMethodrefConstant, referencedClass, referencedMember); } // Update the name and type index. interfaceMethodrefConstant.u2nameAndTypeIndex = new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType); // Remember that the stack sizes of the methods in this class // may have changed. stackSizesMayHaveChanged = true; } // Check if this is an interface method. isInterfaceMethod = true; clazz.constantPoolEntryAccept(interfaceMethodrefConstant.u2classIndex, this); // Has the method become a non-interface method? if (!isInterfaceMethod) { if (DEBUG) { System.out.println("MemberReferenceFixer:"); System.out.println(" Class file = "+clazz.getName()); System.out.println(" Ref class = "+referencedClass.getName()); System.out.println(" Ref method = "+interfaceMethodrefConstant.getName(clazz)+interfaceMethodrefConstant.getType(clazz)); System.out.println(" -> ordinary method"); } // Replace the interface method reference by a method reference. ((ProgramClass)clazz).constantPool[this.constantIndex] = new MethodrefConstant(interfaceMethodrefConstant.u2classIndex, interfaceMethodrefConstant.u2nameAndTypeIndex, referencedClass, referencedMember); } } } public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { // Do we know the referenced method? Member referencedMember = methodrefConstant.referencedMember; if (referencedMember != null) { Clazz referencedClass = methodrefConstant.referencedClass; // Does it have a new name or type? String newName = referencedMember.getName(referencedClass); String newType = referencedMember.getDescriptor(referencedClass); if (!methodrefConstant.getName(clazz).equals(newName) || !methodrefConstant.getType(clazz).equals(newType)) { if (DEBUG) { debug(clazz, methodrefConstant, referencedClass, referencedMember); } // Update the name and type index. methodrefConstant.u2nameAndTypeIndex = new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType); // Remember that the stack sizes of the methods in this class // may have changed. stackSizesMayHaveChanged = true; } // Check if this is an interface method. isInterfaceMethod = false; clazz.constantPoolEntryAccept(methodrefConstant.u2classIndex, this); // Has the method become an interface method? if (isInterfaceMethod) { if (DEBUG) { System.out.println("MemberReferenceFixer:"); System.out.println(" Class file = "+clazz.getName()); System.out.println(" Ref class = "+referencedClass.getName()); System.out.println(" Ref method = "+methodrefConstant.getName(clazz)+methodrefConstant.getType(clazz)); System.out.println(" -> interface method"); } // Replace the method reference by an interface method reference. ((ProgramClass)clazz).constantPool[this.constantIndex] = new InterfaceMethodrefConstant(methodrefConstant.u2classIndex, methodrefConstant.u2nameAndTypeIndex, referencedClass, referencedMember); } } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Check if this class entry is an array type. if (ClassUtil.isInternalArrayType(classConstant.getName(clazz))) { isInterfaceMethod = false; } else { // Check if this class entry refers to an interface class. Clazz referencedClass = classConstant.referencedClass; if (referencedClass != null) { isInterfaceMethod = (referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0; } } } // Implementations for MemberVisitor. public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) { // Fix the attributes. programMember.attributesAccept(programClass, this); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { Member referencedMember = enclosingMethodAttribute.referencedMethod; if (referencedMember != null) { Clazz referencedClass = enclosingMethodAttribute.referencedClass; // Does it have a new name or type? String newName = referencedMember.getName(referencedClass); String newType = referencedMember.getDescriptor(referencedClass); if (!enclosingMethodAttribute.getName(clazz).equals(newName) || !enclosingMethodAttribute.getType(clazz).equals(newType)) { // Update the name and type index. enclosingMethodAttribute.u2nameAndTypeIndex = new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType); } } } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Recompute the maximum stack size if necessary. if (stackSizesMayHaveChanged) { stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); } // Fix the nested attributes. codeAttribute.attributesAccept(clazz, method, this); } 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 AnnotationVisitor. public void visitAnnotation(Clazz clazz, Annotation annotation) { // Fix the element values. annotation.elementValuesAccept(clazz, this); } // Implementations for ElementValueVisitor. public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { fixElementValue(clazz, annotation, constantElementValue); } public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { fixElementValue(clazz, annotation, enumConstantElementValue); } public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { fixElementValue(clazz, annotation, classElementValue); } public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { fixElementValue(clazz, annotation, annotationElementValue); // Fix the annotation. annotationElementValue.annotationAccept(clazz, this); } public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { fixElementValue(clazz, annotation, arrayElementValue); // Fix the element values. arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Small utility methods. /** * Fixes the method reference of the element value, if any. */ private void fixElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) { // Do we know the referenced method? Member referencedMember = elementValue.referencedMethod; if (referencedMember != null) { // Does it have a new name or type? String methodName = elementValue.getMethodName(clazz); String newMethodName = referencedMember.getName(elementValue.referencedClass); if (!methodName.equals(newMethodName)) { // Update the element name index. elementValue.u2elementNameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newMethodName); } } } private void debug(Clazz clazz, StringConstant stringConstant, Clazz referencedClass, Member referencedMember) { System.out.println("MemberReferenceFixer:"); System.out.println(" ["+clazz.getName()+"]: String ["+ stringConstant.getString(clazz)+"] -> ["+ referencedClass.getName()+"."+referencedMember.getName(referencedClass)+" "+referencedMember.getDescriptor(referencedClass)+"]"); } private void debug(Clazz clazz, RefConstant refConstant, Clazz referencedClass, Member referencedMember) { System.out.println("MemberReferenceFixer:"); System.out.println(" ["+clazz.getName()+"]: ["+ refConstant.getClassName(clazz)+"."+refConstant.getName(clazz)+" "+refConstant.getType(clazz)+"] -> ["+ referencedClass.getName()+"."+referencedMember.getName(referencedClass)+" "+referencedMember.getDescriptor(referencedClass)+"]"); } }