From b72c5c2e5482cf10117b2b25f642f7616b2326c3 Mon Sep 17 00:00:00 2001 From: Joe Onorato Date: Mon, 31 Aug 2009 10:12:00 -0700 Subject: ProGuard 4.4 --- src/proguard/classfile/editor/AccessFixer.java | 164 +++ src/proguard/classfile/editor/AnnotationAdder.java | 153 +++ .../editor/AnnotationsAttributeEditor.java | 67 ++ src/proguard/classfile/editor/AttributeAdder.java | 457 ++++++++ src/proguard/classfile/editor/AttributeSorter.java | 89 ++ .../classfile/editor/AttributesEditor.java | 269 +++++ src/proguard/classfile/editor/ClassEditor.java | 255 +++++ .../classfile/editor/ClassElementSorter.java | 52 + .../classfile/editor/ClassMemberSorter.java | 69 ++ .../classfile/editor/ClassReferenceFixer.java | 546 +++++++++ .../classfile/editor/CodeAttributeComposer.java | 845 ++++++++++++++ .../classfile/editor/CodeAttributeEditor.java | 1163 ++++++++++++++++++++ .../editor/CodeAttributeEditorResetter.java | 60 + .../classfile/editor/ComparableConstant.java | 200 ++++ src/proguard/classfile/editor/ConstantAdder.java | 194 ++++ .../classfile/editor/ConstantPoolEditor.java | 665 +++++++++++ .../classfile/editor/ConstantPoolRemapper.java | 617 +++++++++++ .../classfile/editor/ConstantPoolSorter.java | 126 +++ .../classfile/editor/ElementValueAdder.java | 217 ++++ .../classfile/editor/ElementValuesEditor.java | 238 ++++ src/proguard/classfile/editor/ExceptionAdder.java | 65 ++ .../classfile/editor/ExceptionInfoAdder.java | 67 ++ .../editor/ExceptionsAttributeEditor.java | 68 ++ .../classfile/editor/InstructionAdder.java | 76 ++ .../classfile/editor/InstructionWriter.java | 278 +++++ src/proguard/classfile/editor/InterfaceAdder.java | 62 ++ src/proguard/classfile/editor/InterfaceSorter.java | 67 ++ .../classfile/editor/InterfacesEditor.java | 122 ++ .../classfile/editor/LineNumberInfoAdder.java | 59 + .../editor/LineNumberTableAttributeEditor.java | 67 ++ .../classfile/editor/LocalVariableInfoAdder.java | 67 ++ .../editor/LocalVariableTableAttributeEditor.java | 67 ++ .../editor/LocalVariableTypeInfoAdder.java | 68 ++ .../LocalVariableTypeTableAttributeEditor.java | 68 ++ src/proguard/classfile/editor/MemberAdder.java | 257 +++++ .../classfile/editor/MemberReferenceFixer.java | 456 ++++++++ .../classfile/editor/MethodInvocationFixer.java | 254 +++++ .../classfile/editor/NamedAttributeDeleter.java | 54 + .../ParameterAnnotationsAttributeEditor.java | 71 ++ .../classfile/editor/StackSizeUpdater.java | 54 + src/proguard/classfile/editor/SubclassAdder.java | 59 + src/proguard/classfile/editor/SubclassToAdder.java | 60 + src/proguard/classfile/editor/VariableCleaner.java | 135 +++ src/proguard/classfile/editor/VariableEditor.java | 129 +++ .../classfile/editor/VariableRemapper.java | 197 ++++ .../classfile/editor/VariableSizeUpdater.java | 98 ++ src/proguard/classfile/editor/package.html | 3 + 47 files changed, 9474 insertions(+) create mode 100644 src/proguard/classfile/editor/AccessFixer.java create mode 100644 src/proguard/classfile/editor/AnnotationAdder.java create mode 100644 src/proguard/classfile/editor/AnnotationsAttributeEditor.java create mode 100644 src/proguard/classfile/editor/AttributeAdder.java create mode 100644 src/proguard/classfile/editor/AttributeSorter.java create mode 100644 src/proguard/classfile/editor/AttributesEditor.java create mode 100644 src/proguard/classfile/editor/ClassEditor.java create mode 100644 src/proguard/classfile/editor/ClassElementSorter.java create mode 100644 src/proguard/classfile/editor/ClassMemberSorter.java create mode 100644 src/proguard/classfile/editor/ClassReferenceFixer.java create mode 100644 src/proguard/classfile/editor/CodeAttributeComposer.java create mode 100644 src/proguard/classfile/editor/CodeAttributeEditor.java create mode 100644 src/proguard/classfile/editor/CodeAttributeEditorResetter.java create mode 100644 src/proguard/classfile/editor/ComparableConstant.java create mode 100644 src/proguard/classfile/editor/ConstantAdder.java create mode 100644 src/proguard/classfile/editor/ConstantPoolEditor.java create mode 100644 src/proguard/classfile/editor/ConstantPoolRemapper.java create mode 100644 src/proguard/classfile/editor/ConstantPoolSorter.java create mode 100644 src/proguard/classfile/editor/ElementValueAdder.java create mode 100644 src/proguard/classfile/editor/ElementValuesEditor.java create mode 100644 src/proguard/classfile/editor/ExceptionAdder.java create mode 100644 src/proguard/classfile/editor/ExceptionInfoAdder.java create mode 100644 src/proguard/classfile/editor/ExceptionsAttributeEditor.java create mode 100644 src/proguard/classfile/editor/InstructionAdder.java create mode 100644 src/proguard/classfile/editor/InstructionWriter.java create mode 100644 src/proguard/classfile/editor/InterfaceAdder.java create mode 100644 src/proguard/classfile/editor/InterfaceSorter.java create mode 100644 src/proguard/classfile/editor/InterfacesEditor.java create mode 100644 src/proguard/classfile/editor/LineNumberInfoAdder.java create mode 100644 src/proguard/classfile/editor/LineNumberTableAttributeEditor.java create mode 100644 src/proguard/classfile/editor/LocalVariableInfoAdder.java create mode 100644 src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java create mode 100644 src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java create mode 100644 src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java create mode 100644 src/proguard/classfile/editor/MemberAdder.java create mode 100644 src/proguard/classfile/editor/MemberReferenceFixer.java create mode 100644 src/proguard/classfile/editor/MethodInvocationFixer.java create mode 100644 src/proguard/classfile/editor/NamedAttributeDeleter.java create mode 100644 src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java create mode 100644 src/proguard/classfile/editor/StackSizeUpdater.java create mode 100644 src/proguard/classfile/editor/SubclassAdder.java create mode 100644 src/proguard/classfile/editor/SubclassToAdder.java create mode 100644 src/proguard/classfile/editor/VariableCleaner.java create mode 100644 src/proguard/classfile/editor/VariableEditor.java create mode 100644 src/proguard/classfile/editor/VariableRemapper.java create mode 100644 src/proguard/classfile/editor/VariableSizeUpdater.java create mode 100644 src/proguard/classfile/editor/package.html (limited to 'src/proguard/classfile/editor') diff --git a/src/proguard/classfile/editor/AccessFixer.java b/src/proguard/classfile/editor/AccessFixer.java new file mode 100644 index 0000000..7d6274e --- /dev/null +++ b/src/proguard/classfile/editor/AccessFixer.java @@ -0,0 +1,164 @@ +/* + * 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.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.util.*; +import proguard.classfile.visitor.*; + +/** + * This ConstantVisitor fixes the access modifiers of all classes and class + * members that are referenced by the constants that it visits. + * + * @author Eric Lafortune + */ +public class AccessFixer +extends SimplifiedVisitor +implements ConstantVisitor, + ClassVisitor, + MemberVisitor +{ + private MyReferencedClassFinder referencedClassFinder = new MyReferencedClassFinder(); + + private Clazz referencingClass; + private Clazz referencedClass; + + + // Implementations for ConstantVisitor. + + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + + public void visitStringConstant(Clazz clazz, StringConstant stringConstant) + { + referencingClass = clazz; + referencedClass = stringConstant.referencedClass; + + // Make sure the access flags of the referenced class or class member, + // if any, are acceptable. + stringConstant.referencedClassAccept(this); + stringConstant.referencedMemberAccept(this); + } + + + public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) + { + referencingClass = clazz; + + // Remember the specified class, since it might be different from + // the referenced class that acutally contains the class member. + clazz.constantPoolEntryAccept(refConstant.u2classIndex, referencedClassFinder); + + // Make sure the access flags of the referenced class member are + // acceptable. + refConstant.referencedMemberAccept(this); + } + + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + referencingClass = clazz; + + // Make sure the access flags of the referenced class are acceptable. + classConstant.referencedClassAccept(this); + } + + + // Implementations for ClassVisitor. + + public void visitLibraryClass(LibraryClass libraryClass) {} + + + public void visitProgramClass(ProgramClass programClass) + { + int currentAccessFlags = programClass.getAccessFlags(); + int currentAccessLevel = AccessUtil.accessLevel(currentAccessFlags); + + // Compute the required access level. + Clazz referencingClass = this.referencingClass; + int requiredAccessLevel = + inSamePackage(programClass, referencingClass) ? AccessUtil.PACKAGE_VISIBLE : + AccessUtil.PUBLIC; + + // Fix the class access flags if necessary. + if (currentAccessLevel < requiredAccessLevel) + { + programClass.u2accessFlags = + AccessUtil.replaceAccessFlags(currentAccessFlags, + AccessUtil.accessFlags(requiredAccessLevel)); + } + } + + + // Implementations for MemberVisitor. + + public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember) {} + + + public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) + { + int currentAccessFlags = programMember.getAccessFlags(); + int currentAccessLevel = AccessUtil.accessLevel(currentAccessFlags); + + // Compute the required access level. + int requiredAccessLevel = + programClass.equals(referencingClass) ? AccessUtil.PRIVATE : + inSamePackage(programClass, referencingClass) ? AccessUtil.PACKAGE_VISIBLE : + referencedClass.extends_(referencingClass) && + referencingClass.extends_(programClass) ? AccessUtil.PROTECTED : + AccessUtil.PUBLIC; + + // Fix the class member access flags if necessary. + if (currentAccessLevel < requiredAccessLevel) + { + programMember.u2accessFlags = + AccessUtil.replaceAccessFlags(currentAccessFlags, + AccessUtil.accessFlags(requiredAccessLevel)); + } + } + + + /** + * This ConstantVisitor returns the referenced class of the class constant + * that it visits. + */ + private class MyReferencedClassFinder + extends SimplifiedVisitor + implements ConstantVisitor + { + // Implementations for ConstantVisitor. + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + referencedClass = classConstant.referencedClass; + } + } + + + // Small utility methods. + + private boolean inSamePackage(ProgramClass class1, Clazz class2) + { + return ClassUtil.internalPackageName(class1.getName()).equals( + ClassUtil.internalPackageName(class2.getName())); + } +} diff --git a/src/proguard/classfile/editor/AnnotationAdder.java b/src/proguard/classfile/editor/AnnotationAdder.java new file mode 100644 index 0000000..359164a --- /dev/null +++ b/src/proguard/classfile/editor/AnnotationAdder.java @@ -0,0 +1,153 @@ +/* + * 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.annotation.*; +import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AnnotationVisitor adds all annotations that it visits to the given + * target annotation element value, target annotation attribute, or target + * parameter annotation attribute. + * + * @author Eric Lafortune + */ +public class AnnotationAdder +extends SimplifiedVisitor +implements AnnotationVisitor +{ + private static final ElementValue[] EMPTY_ELEMENT_VALUES = new ElementValue[0]; + + + private final ProgramClass targetClass; + private final AnnotationElementValue targetAnnotationElementValue; + private final AnnotationsAttributeEditor annotationsAttributeEditor; + private final ParameterAnnotationsAttributeEditor parameterAnnotationsAttributeEditor; + + private final ConstantAdder constantAdder; + + + /** + * Creates a new AnnotationAdder that will copy annotations into the given + * target annotation element value. + */ + public AnnotationAdder(ProgramClass targetClass, + AnnotationElementValue targetAnnotationElementValue) + { + this.targetClass = targetClass; + this.targetAnnotationElementValue = targetAnnotationElementValue; + this.annotationsAttributeEditor = null; + this.parameterAnnotationsAttributeEditor = null; + + constantAdder = new ConstantAdder(targetClass); + } + + + /** + * Creates a new AnnotationAdder that will copy annotations into the given + * target annotations attribute. + */ + public AnnotationAdder(ProgramClass targetClass, + AnnotationsAttribute targetAnnotationsAttribute) + { + this.targetClass = targetClass; + this.targetAnnotationElementValue = null; + this.annotationsAttributeEditor = new AnnotationsAttributeEditor(targetAnnotationsAttribute); + this.parameterAnnotationsAttributeEditor = null; + + constantAdder = new ConstantAdder(targetClass); + } + + + /** + * Creates a new AnnotationAdder that will copy annotations into the given + * target parameter annotations attribute. + */ + public AnnotationAdder(ProgramClass targetClass, + ParameterAnnotationsAttribute targetParameterAnnotationsAttribute) + { + this.targetClass = targetClass; + this.targetAnnotationElementValue = null; + this.annotationsAttributeEditor = null; + this.parameterAnnotationsAttributeEditor = new ParameterAnnotationsAttributeEditor(targetParameterAnnotationsAttribute); + + constantAdder = new ConstantAdder(targetClass); + } + + + // Implementations for AnnotationVisitor. + + public void visitAnnotation(Clazz clazz, Annotation annotation) + { + Annotation newAnnotation = + new Annotation(constantAdder.addConstant(clazz, annotation.u2typeIndex), + 0, + annotation.u2elementValuesCount > 0 ? + new ElementValue[annotation.u2elementValuesCount] : + EMPTY_ELEMENT_VALUES); + + // TODO: Clone array. + newAnnotation.referencedClasses = annotation.referencedClasses; + + // Add the element values. + annotation.elementValuesAccept(clazz, + new ElementValueAdder(targetClass, + newAnnotation, + false)); + + // What's the target? + if (targetAnnotationElementValue != null) + { + // Simply set the completed annotation. + targetAnnotationElementValue.annotationValue = newAnnotation; + } + else + { + // Add the completed annotation. + annotationsAttributeEditor.addAnnotation(newAnnotation); + } + } + + + public void visitAnnotation(Clazz clazz, Method method, int parameterIndex, Annotation annotation) + { + Annotation newAnnotation = + new Annotation(constantAdder.addConstant(clazz, annotation.u2typeIndex), + 0, + annotation.u2elementValuesCount > 0 ? + new ElementValue[annotation.u2elementValuesCount] : + EMPTY_ELEMENT_VALUES); + + // TODO: Clone array. + newAnnotation.referencedClasses = annotation.referencedClasses; + + // Add the element values. + annotation.elementValuesAccept(clazz, + new ElementValueAdder(targetClass, + newAnnotation, + false)); + + // Add the completed annotation. + parameterAnnotationsAttributeEditor.addAnnotation(parameterIndex, newAnnotation); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/AnnotationsAttributeEditor.java b/src/proguard/classfile/editor/AnnotationsAttributeEditor.java new file mode 100644 index 0000000..bf8852c --- /dev/null +++ b/src/proguard/classfile/editor/AnnotationsAttributeEditor.java @@ -0,0 +1,67 @@ +/* + * 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.attribute.annotation.*; + +/** + * This class can add annotations to a given annotations attribute. + * Annotations to be added must have been filled out beforehand. + * + * @author Eric Lafortune + */ +public class AnnotationsAttributeEditor +{ + private AnnotationsAttribute targetAnnotationsAttribute; + + + /** + * Creates a new AnnotationsAttributeEditor that will edit annotations in + * the given annotations attribute. + */ + public AnnotationsAttributeEditor(AnnotationsAttribute targetAnnotationsAttribute) + { + this.targetAnnotationsAttribute = targetAnnotationsAttribute; + } + + + /** + * Adds a given annotation to the annotations attribute. + */ + public void addAnnotation(Annotation annotation) + { + int annotationsCount = targetAnnotationsAttribute.u2annotationsCount; + Annotation[] annotations = targetAnnotationsAttribute.annotations; + + // Make sure there is enough space for the new annotation. + if (annotations.length <= annotationsCount) + { + targetAnnotationsAttribute.annotations = new Annotation[annotationsCount+1]; + System.arraycopy(annotations, 0, + targetAnnotationsAttribute.annotations, 0, + annotationsCount); + annotations = targetAnnotationsAttribute.annotations; + } + + // Add the annotation. + annotations[targetAnnotationsAttribute.u2annotationsCount++] = annotation; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/AttributeAdder.java b/src/proguard/classfile/editor/AttributeAdder.java new file mode 100644 index 0000000..2b610b7 --- /dev/null +++ b/src/proguard/classfile/editor/AttributeAdder.java @@ -0,0 +1,457 @@ +/* + * 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.preverification.*; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AttributeVisitor adds all attributes that it visits to the given + * target class, class member, or attribute. + * + * @author Eric Lafortune + */ +public class AttributeAdder +extends SimplifiedVisitor +implements AttributeVisitor +{ + private static final byte[] EMPTY_BYTES = new byte[0]; + private static final int[] EMPTY_INTS = new int[0]; + private static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0]; + private static final ExceptionInfo[] EMPTY_EXCEPTIONS = new ExceptionInfo[0]; + + + private final ProgramClass targetClass; + private final ProgramMember targetMember; + private final CodeAttribute targetCodeAttribute; + private final boolean replaceAttributes; + + private final ConstantAdder constantAdder; + private final AttributesEditor attributesEditor; + + + /** + * Creates a new AttributeAdder that will copy attributes into the given + * target class. + */ + public AttributeAdder(ProgramClass targetClass, + boolean replaceAttributes) + { + this(targetClass, null, null, replaceAttributes); + } + + + /** + * Creates a new AttributeAdder that will copy attributes into the given + * target class member. + */ + public AttributeAdder(ProgramClass targetClass, + ProgramMember targetMember, + boolean replaceAttributes) + { + this(targetClass, targetMember, null, replaceAttributes); + } + + + /** + * Creates a new AttributeAdder that will copy attributes into the given + * target attribute. + */ + public AttributeAdder(ProgramClass targetClass, + ProgramMember targetMember, + CodeAttribute targetCodeAttribute, + boolean replaceAttributes) + { + this.targetClass = targetClass; + this.targetMember = targetMember; + this.targetCodeAttribute = targetCodeAttribute; + this.replaceAttributes = replaceAttributes; + + constantAdder = new ConstantAdder(targetClass); + attributesEditor = new AttributesEditor(targetClass, + targetMember, + targetCodeAttribute, + replaceAttributes); + } + + + // Implementations for AttributeVisitor. + + public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute) + { + // Create a copy of the attribute. + UnknownAttribute newUnknownAttribute = + new UnknownAttribute(constantAdder.addConstant(clazz, unknownAttribute.u2attributeNameIndex), + unknownAttribute.u4attributeLength, + unknownAttribute.info); + + // Add it to the target class. + attributesEditor.addAttribute(newUnknownAttribute); + } + + + public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute) + { + // Create a copy of the attribute. + SourceFileAttribute newSourceFileAttribute = + new SourceFileAttribute(constantAdder.addConstant(clazz, sourceFileAttribute.u2attributeNameIndex), + constantAdder.addConstant(clazz, sourceFileAttribute.u2sourceFileIndex)); + + // Add it to the target class. + attributesEditor.addAttribute(newSourceFileAttribute); + } + + + public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute) + { + // Create a copy of the attribute. + SourceDirAttribute newSourceDirAttribute = + new SourceDirAttribute(constantAdder.addConstant(clazz, sourceDirAttribute.u2attributeNameIndex), + constantAdder.addConstant(clazz, sourceDirAttribute.u2sourceDirIndex)); + + // Add it to the target class. + attributesEditor.addAttribute(newSourceDirAttribute); + } + + + public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) + { + // TODO: Implement method. + // Note that the attribute may already be present. +// // Create a copy of the attribute. +// InnerClassesAttribute newInnerClassesAttribute = +// new InnerClassesAttribute(constantAdder.addConstant(clazz, innerClassesAttribute.u2attributeNameIndex), +// 0, +// null); +// +// // Add it to the target class. +// attributesEditor.addClassAttribute(newInnerClassesAttribute); + } + + + public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) + { + // Create a copy of the attribute. + EnclosingMethodAttribute newEnclosingMethodAttribute = + new EnclosingMethodAttribute(constantAdder.addConstant(clazz, enclosingMethodAttribute.u2attributeNameIndex), + constantAdder.addConstant(clazz, enclosingMethodAttribute.u2classIndex), + enclosingMethodAttribute.u2nameAndTypeIndex == 0 ? 0 : + constantAdder.addConstant(clazz, enclosingMethodAttribute.u2nameAndTypeIndex)); + + newEnclosingMethodAttribute.referencedClass = enclosingMethodAttribute.referencedClass; + newEnclosingMethodAttribute.referencedMethod = enclosingMethodAttribute.referencedMethod; + + // Add it to the target class. + attributesEditor.addAttribute(newEnclosingMethodAttribute); + } + + + public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute) + { + // Create a copy of the attribute. + DeprecatedAttribute newDeprecatedAttribute = + new DeprecatedAttribute(constantAdder.addConstant(clazz, deprecatedAttribute.u2attributeNameIndex)); + + // Add it to the target. + attributesEditor.addAttribute(newDeprecatedAttribute); + } + + + public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute) + { + // Create a copy of the attribute. + SyntheticAttribute newSyntheticAttribute = + new SyntheticAttribute(constantAdder.addConstant(clazz, syntheticAttribute.u2attributeNameIndex)); + + // Add it to the target. + attributesEditor.addAttribute(newSyntheticAttribute); + } + + + public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) + { + // Create a copy of the attribute. + SignatureAttribute newSignatureAttribute = + new SignatureAttribute(constantAdder.addConstant(clazz, signatureAttribute.u2attributeNameIndex), + constantAdder.addConstant(clazz, signatureAttribute.u2signatureIndex)); + + newSignatureAttribute.referencedClasses = signatureAttribute.referencedClasses; + + // Add it to the target. + attributesEditor.addAttribute(newSignatureAttribute); + } + + + public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute) + { + // Create a copy of the attribute. + ConstantValueAttribute newConstantValueAttribute = + new ConstantValueAttribute(constantAdder.addConstant(clazz, constantValueAttribute.u2attributeNameIndex), + constantAdder.addConstant(clazz, constantValueAttribute.u2constantValueIndex)); + + // Add it to the target field. + attributesEditor.addAttribute(newConstantValueAttribute); + } + + + public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute) + { + // Create a new exceptions attribute. + ExceptionsAttribute newExceptionsAttribute = + new ExceptionsAttribute(constantAdder.addConstant(clazz, exceptionsAttribute.u2attributeNameIndex), + 0, + exceptionsAttribute.u2exceptionIndexTableLength > 0 ? + new int[exceptionsAttribute.u2exceptionIndexTableLength] : + EMPTY_INTS); + + // Add the exceptions. + exceptionsAttribute.exceptionEntriesAccept((ProgramClass)clazz, + new ExceptionAdder(targetClass, + newExceptionsAttribute)); + + // Add it to the target method. + attributesEditor.addAttribute(newExceptionsAttribute); + } + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Create a new code attribute. + CodeAttribute newCodeAttribute = + new CodeAttribute(constantAdder.addConstant(clazz, codeAttribute.u2attributeNameIndex), + codeAttribute.u2maxStack, + codeAttribute.u2maxLocals, + 0, + EMPTY_BYTES, + 0, + codeAttribute.u2exceptionTableLength > 0 ? + new ExceptionInfo[codeAttribute.u2exceptionTableLength] : + EMPTY_EXCEPTIONS, + 0, + codeAttribute.u2attributesCount > 0 ? + new Attribute[codeAttribute.u2attributesCount] : + EMPTY_ATTRIBUTES); + + CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); + + codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); + + // Add the instructions. + codeAttribute.instructionsAccept(clazz, + method, + new InstructionAdder(targetClass, + codeAttributeComposer)); + + // Append a label just after the code. + codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); + + // Add the exceptions. + codeAttribute.exceptionsAccept(clazz, + method, + new ExceptionInfoAdder(targetClass, + codeAttributeComposer)); + + codeAttributeComposer.endCodeFragment(); + + // Add the attributes. + codeAttribute.attributesAccept(clazz, + method, + new AttributeAdder(targetClass, + targetMember, + newCodeAttribute, + replaceAttributes)); + + // Apply these changes to the new code attribute. + codeAttributeComposer.visitCodeAttribute(targetClass, + (Method)targetMember, + newCodeAttribute); + + // Add the completed code attribute to the target method. + attributesEditor.addAttribute(newCodeAttribute); + } + + + public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) + { + // TODO: Implement method. + } + + + public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) + { + // TODO: Implement method. + } + + + public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) + { + // Create a new line number table attribute. + LineNumberTableAttribute newLineNumberTableAttribute = + new LineNumberTableAttribute(constantAdder.addConstant(clazz, lineNumberTableAttribute.u2attributeNameIndex), + 0, + new LineNumberInfo[lineNumberTableAttribute.u2lineNumberTableLength]); + + // Add the line numbers. + lineNumberTableAttribute.lineNumbersAccept(clazz, + method, + codeAttribute, + new LineNumberInfoAdder(newLineNumberTableAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newLineNumberTableAttribute); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + // Create a new local variable table attribute. + LocalVariableTableAttribute newLocalVariableTableAttribute = + new LocalVariableTableAttribute(constantAdder.addConstant(clazz, localVariableTableAttribute.u2attributeNameIndex), + 0, + new LocalVariableInfo[localVariableTableAttribute.u2localVariableTableLength]); + + // Add the local variables. + localVariableTableAttribute.localVariablesAccept(clazz, + method, + codeAttribute, + new LocalVariableInfoAdder(targetClass, newLocalVariableTableAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newLocalVariableTableAttribute); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + // Create a new local variable type table attribute. + LocalVariableTypeTableAttribute newLocalVariableTypeTableAttribute = + new LocalVariableTypeTableAttribute(constantAdder.addConstant(clazz, localVariableTypeTableAttribute.u2attributeNameIndex), + 0, + new LocalVariableTypeInfo[localVariableTypeTableAttribute.u2localVariableTypeTableLength]); + + // Add the local variable types. + localVariableTypeTableAttribute.localVariablesAccept(clazz, + method, + codeAttribute, + new LocalVariableTypeInfoAdder(targetClass, newLocalVariableTypeTableAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newLocalVariableTypeTableAttribute); + } + + + public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute) + { + // Create a new annotations attribute. + RuntimeVisibleAnnotationsAttribute newAnnotationsAttribute = + new RuntimeVisibleAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeVisibleAnnotationsAttribute.u2attributeNameIndex), + 0, + new Annotation[runtimeVisibleAnnotationsAttribute.u2annotationsCount]); + + // Add the annotations. + runtimeVisibleAnnotationsAttribute.annotationsAccept(clazz, + new AnnotationAdder(targetClass, + newAnnotationsAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newAnnotationsAttribute); + } + + + public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute) + { + // Create a new annotations attribute. + RuntimeInvisibleAnnotationsAttribute newAnnotationsAttribute = + new RuntimeInvisibleAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeInvisibleAnnotationsAttribute.u2attributeNameIndex), + 0, + new Annotation[runtimeInvisibleAnnotationsAttribute.u2annotationsCount]); + + // Add the annotations. + runtimeInvisibleAnnotationsAttribute.annotationsAccept(clazz, + new AnnotationAdder(targetClass, + newAnnotationsAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newAnnotationsAttribute); + } + + + public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute) + { + // Create a new annotations attribute. + RuntimeVisibleParameterAnnotationsAttribute newParameterAnnotationsAttribute = + new RuntimeVisibleParameterAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeVisibleParameterAnnotationsAttribute.u2attributeNameIndex), + 0, + new int[runtimeVisibleParameterAnnotationsAttribute.u2parametersCount], + new Annotation[runtimeVisibleParameterAnnotationsAttribute.u2parametersCount][]); + + // Add the annotations. + runtimeVisibleParameterAnnotationsAttribute.annotationsAccept(clazz, + method, + new AnnotationAdder(targetClass, + newParameterAnnotationsAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newParameterAnnotationsAttribute); + } + + + public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute) + { + // Create a new annotations attribute. + RuntimeInvisibleParameterAnnotationsAttribute newParameterAnnotationsAttribute = + new RuntimeInvisibleParameterAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeInvisibleParameterAnnotationsAttribute.u2attributeNameIndex), + 0, + new int[runtimeInvisibleParameterAnnotationsAttribute.u2parametersCount], + new Annotation[runtimeInvisibleParameterAnnotationsAttribute.u2parametersCount][]); + + // Add the annotations. + runtimeInvisibleParameterAnnotationsAttribute.annotationsAccept(clazz, + method, + new AnnotationAdder(targetClass, + newParameterAnnotationsAttribute)); + + // Add it to the target. + attributesEditor.addAttribute(newParameterAnnotationsAttribute); + } + + + public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) + { + // Create a new annotation default attribute. + AnnotationDefaultAttribute newAnnotationDefaultAttribute = + new AnnotationDefaultAttribute(constantAdder.addConstant(clazz, annotationDefaultAttribute.u2attributeNameIndex), + null); + + // Add the annotations. + annotationDefaultAttribute.defaultValueAccept(clazz, + new ElementValueAdder(targetClass, + newAnnotationDefaultAttribute, + false)); + + // Add it to the target. + attributesEditor.addAttribute(newAnnotationDefaultAttribute); + } +} diff --git a/src/proguard/classfile/editor/AttributeSorter.java b/src/proguard/classfile/editor/AttributeSorter.java new file mode 100644 index 0000000..d8e3367 --- /dev/null +++ b/src/proguard/classfile/editor/AttributeSorter.java @@ -0,0 +1,89 @@ +/* + * 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.visitor.AttributeVisitor; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.*; + +import java.util.*; + +/** + * This ClassVisitor sorts the attributes of the classes that it visits. + * The sorting order is based on the types of the attributes. + * + * @author Eric Lafortune + */ +public class AttributeSorter +extends SimplifiedVisitor +implements ClassVisitor, MemberVisitor, AttributeVisitor, Comparator +{ + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + // Sort the attributes. + Arrays.sort(programClass.attributes, 0, programClass.u2attributesCount, this); + + // Sort the attributes of the class members. + programClass.fieldsAccept(this); + programClass.methodsAccept(this); + } + + + // Implementations for MemberVisitor. + + public void visitProgramMember(ProgramClass programClass, ProgramMember programMember) + { + // Sort the attributes. + Arrays.sort(programMember.attributes, 0, programMember.u2attributesCount, this); + + // Sort the attributes of the attributes. + programMember.attributesAccept(programClass, this); + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Sort the attributes. + Arrays.sort(codeAttribute.attributes, 0, codeAttribute.u2attributesCount, this); + } + + + // Implementations for Comparator. + + public int compare(Object object1, Object object2) + { + Attribute attribute1 = (Attribute)object1; + Attribute attribute2 = (Attribute)object2; + + return attribute1.u2attributeNameIndex < attribute2.u2attributeNameIndex ? -1 : + attribute1.u2attributeNameIndex > attribute2.u2attributeNameIndex ? 1 : + 0; + } +} diff --git a/src/proguard/classfile/editor/AttributesEditor.java b/src/proguard/classfile/editor/AttributesEditor.java new file mode 100644 index 0000000..10846cc --- /dev/null +++ b/src/proguard/classfile/editor/AttributesEditor.java @@ -0,0 +1,269 @@ +/* + * 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.*; + +/** + * This class can add and delete attributes to and from classes, fields, + * methods, and code attributes. Attributes to be added must be filled out + * beforehand, including their references to the constant pool. Existing + * attributes of the same type are always replaced. + * + * @author Eric Lafortune + */ +public class AttributesEditor +{ + private final ProgramClass targetClass; + private final ProgramMember targetMember; + private final CodeAttribute targetAttribute; + private final boolean replaceAttributes; + + + /** + * Creates a new AttributeAdder that will edit attributes in the given + * target class. + */ + public AttributesEditor(ProgramClass targetClass, + boolean replaceAttributes) + { + this(targetClass, null, null, replaceAttributes); + } + + + /** + * Creates a new AttributeAdder that will edit attributes in the given + * target class member. + */ + public AttributesEditor(ProgramClass targetClass, + ProgramMember targetMember, + boolean replaceAttributes) + { + this(targetClass, targetMember, null, replaceAttributes); + } + + + /** + * Creates a new AttributeAdder that will edit attributes in the given + * target code attribute. + */ + public AttributesEditor(ProgramClass targetClass, + ProgramMember targetMember, + CodeAttribute targetAttribute, + boolean replaceAttributes) + { + this.targetClass = targetClass; + this.targetMember = targetMember; + this.targetAttribute = targetAttribute; + this.replaceAttributes = replaceAttributes; + } + + + /** + * Adds the given attribute to the target. + */ + public void addAttribute(Attribute attribute) + { + // What's the target? + if (targetAttribute != null) + { + // Try to replace an existing attribute. + if (!replaceAttributes || + !replaceAttribute(targetAttribute.u2attributesCount, + targetAttribute.attributes, + attribute)) + { + // Otherwise append the attribute. + targetAttribute.attributes = + addAttribute(targetAttribute.u2attributesCount, + targetAttribute.attributes, + attribute); + + targetAttribute.u2attributesCount++; + } + } + else if (targetMember != null) + { + // Try to replace an existing attribute. + if (!replaceAttributes || + !replaceAttribute(targetMember.u2attributesCount, + targetMember.attributes, + attribute)) + { + // Otherwise append the attribute. + targetMember.attributes = + addAttribute(targetMember.u2attributesCount, + targetMember.attributes, + attribute); + + targetMember.u2attributesCount++; + } + } + else + { + // Try to replace an existing attribute. + if (!replaceAttributes || + !replaceAttribute(targetClass.u2attributesCount, + targetClass.attributes, + attribute)) + { + // Otherwise append the attribute. + targetClass.attributes = + addAttribute(targetClass.u2attributesCount, + targetClass.attributes, + attribute); + + targetClass.u2attributesCount++; + } + } + } + + + /** + * Deletes the specified attribute from the target. + */ + public void deleteAttribute(String attributeName) + { + // What's the target? + if (targetAttribute != null) + { + targetAttribute.u2attributesCount = + deleteAttribute(targetAttribute.u2attributesCount, + targetAttribute.attributes, + attributeName); + } + else if (targetMember != null) + { + targetMember.u2attributesCount = + deleteAttribute(targetMember.u2attributesCount, + targetMember.attributes, + attributeName); + } + else + { + targetClass.u2attributesCount = + deleteAttribute(targetClass.u2attributesCount, + targetClass.attributes, + attributeName); + } + } + + + // Small utility methods. + + /** + * Tries put the given attribute in place of an existing attribute of the + * same name, returning whether it was present. + */ + private boolean replaceAttribute(int attributesCount, + Attribute[] attributes, + Attribute attribute) + { + // Find the attribute with the same name. + int index = findAttribute(attributesCount, + attributes, + attribute.getAttributeName(targetClass)); + if (index < 0) + { + return false; + } + + attributes[index] = attribute; + + return true; + } + + + /** + * Appends the given attribute to the given array of attributes, creating a + * new array if necessary. + */ + private Attribute[] addAttribute(int attributesCount, + Attribute[] attributes, + Attribute attribute) + { + // Is the array too small to contain the additional attribute? + if (attributes.length <= attributesCount) + { + // Create a new array and copy the attributes into it. + Attribute[] newAttributes = new Attribute[attributesCount + 1]; + System.arraycopy(attributes, 0, + newAttributes, 0, + attributesCount); + attributes = newAttributes; + } + + // Append the attribute. + attributes[attributesCount] = attribute; + + return attributes; + } + + + /** + * Deletes the attributes with the given name from the given array of + * attributes, returning the new number of attributes. + */ + private int deleteAttribute(int attributesCount, + Attribute[] attributes, + String attributeName) + { + // Find the attribute. + int index = findAttribute(attributesCount, + attributes, + attributeName); + if (index < 0) + { + return attributesCount; + } + + // Shift the other attributes in the array. + System.arraycopy(attributes, index + 1, + attributes, index, + attributesCount - index - 1); + + // Clear the last entry in the array. + attributes[--attributesCount] = null; + + return attributesCount; + } + + + /** + * Finds the index of the attribute with the given name in the given + * array of attributes. + */ + private int findAttribute(int attributesCount, + Attribute[] attributes, + String attributeName) + { + for (int index = 0; index < attributesCount; index++) + { + if (attributes[index].getAttributeName(targetClass).equals(attributeName)) + { + return index; + } + } + + return -1; + } +} diff --git a/src/proguard/classfile/editor/ClassEditor.java b/src/proguard/classfile/editor/ClassEditor.java new file mode 100644 index 0000000..e503ea3 --- /dev/null +++ b/src/proguard/classfile/editor/ClassEditor.java @@ -0,0 +1,255 @@ +/* + * 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.*; + +/** + * This class can add interfaces and class members to a given class. + * Elements to be added must be filled out beforehand, including their + * references to the constant pool. + * + * @author Eric Lafortune + */ +public class ClassEditor +{ + private static final boolean DEBUG = false; + + private ProgramClass targetClass; + + + /** + * Creates a new ClassEditor that will edit elements in the given + * target class. + */ + public ClassEditor(ProgramClass targetClass) + { + this.targetClass = targetClass; + } + + + /** + * Adds the given interface. + */ + public void addInterface(int interfaceConstantIndex) + { + int interfacesCount = targetClass.u2interfacesCount; + int[] interfaces = targetClass.u2interfaces; + + // Make sure there is enough space for the new interface. + if (interfaces.length <= interfacesCount) + { + targetClass.u2interfaces = new int[interfacesCount+1]; + System.arraycopy(interfaces, 0, + targetClass.u2interfaces, 0, + interfacesCount); + interfaces = targetClass.u2interfaces; + } + + if (DEBUG) + { + System.out.println(targetClass.getName()+": adding interface ["+targetClass.getClassName(interfaceConstantIndex)+"]"); + } + + // Add the interface. + interfaces[targetClass.u2interfacesCount++] = interfaceConstantIndex; + } + + /** + * Removes the given interface. + */ + public void removeInterface(int interfaceConstantIndex) + { + int interfacesCount = targetClass.u2interfacesCount; + int[] interfaces = targetClass.u2interfaces; + + int interfaceIndex = findInterfaceIndex(interfaceConstantIndex); + + // Shift the interface entries. + System.arraycopy(interfaces, interfaceIndex+1, + interfaces, interfaceIndex, + interfacesCount - interfaceIndex - 1); + + // Clear the last entry. + interfaces[--targetClass.u2interfacesCount] = 0; + } + + + /** + * Finds the index of the given interface in the target class. + */ + + private int findInterfaceIndex(int interfaceConstantIndex) + { + int interfacesCount = targetClass.u2interfacesCount; + int[] interfaces = targetClass.u2interfaces; + + for (int index = 0; index < interfacesCount; index++) + { + if (interfaces[index] == interfaceConstantIndex) + { + return index; + } + } + + return interfacesCount; + } + + + /** + * Adds the given field. + */ + public void addField(Field field) + { + int fieldsCount = targetClass.u2fieldsCount; + Field[] fields = targetClass.fields; + + // Make sure there is enough space for the new field. + if (fields.length <= fieldsCount) + { + targetClass.fields = new ProgramField[fieldsCount+1]; + System.arraycopy(fields, 0, + targetClass.fields, 0, + fieldsCount); + fields = targetClass.fields; + } + + if (DEBUG) + { + System.out.println(targetClass.getName()+": adding field ["+field.getName(targetClass)+" "+field.getDescriptor(targetClass)+"]"); + } + + // Add the field. + fields[targetClass.u2fieldsCount++] = field; + } + + + /** + * Removes the given field. Note that removing a field that is still being + * referenced can cause unpredictable effects. + */ + public void removeField(Field field) + { + int fieldsCount = targetClass.u2fieldsCount; + Field[] fields = targetClass.fields; + + int fieldIndex = findFieldIndex(field); + + // Shift the field entries. + System.arraycopy(fields, fieldIndex+1, + fields, fieldIndex, + fieldsCount - fieldIndex - 1); + + // Clear the last entry. + fields[--targetClass.u2fieldsCount] = null; + } + + + /** + * Finds the index of the given field in the target class. + */ + + private int findFieldIndex(Field field) + { + int fieldsCount = targetClass.u2fieldsCount; + Field[] fields = targetClass.fields; + + for (int index = 0; index < fieldsCount; index++) + { + if (fields[index].equals(field)) + { + return index; + } + } + + return fieldsCount; + } + + + /** + * Adds the given method. + */ + public void addMethod(Method method) + { + int methodsCount = targetClass.u2methodsCount; + Method[] methods = targetClass.methods; + + // Make sure there is enough space for the new method. + if (methods.length <= methodsCount) + { + targetClass.methods = new ProgramMethod[methodsCount+1]; + System.arraycopy(methods, 0, + targetClass.methods, 0, + methodsCount); + methods = targetClass.methods; + } + + if (DEBUG) + { + System.out.println(targetClass.getName()+": adding method ["+method.getName(targetClass)+method.getDescriptor(targetClass)+"]"); + } + + // Add the method. + methods[targetClass.u2methodsCount++] = method; + } + + + /** + * Removes the given method. Note that removing a method that is still being + * referenced can cause unpredictable effects. + */ + public void removeMethod(Method method) + { + int methodsCount = targetClass.u2methodsCount; + Method[] methods = targetClass.methods; + + int methodIndex = findMethodIndex(method); + + // Shift the method entries. + System.arraycopy(methods, methodIndex+1, + methods, methodIndex, + methodsCount - methodIndex - 1); + + // Clear the last entry. + methods[--targetClass.u2methodsCount] = null; + } + + + /** + * Finds the index of the given method in the target class. + */ + + private int findMethodIndex(Method method) + { + int methodsCount = targetClass.u2methodsCount; + Method[] methods = targetClass.methods; + + for (int index = 0; index < methodsCount; index++) + { + if (methods[index].equals(method)) + { + return index; + } + } + + return methodsCount; + } +} diff --git a/src/proguard/classfile/editor/ClassElementSorter.java b/src/proguard/classfile/editor/ClassElementSorter.java new file mode 100644 index 0000000..3256c88 --- /dev/null +++ b/src/proguard/classfile/editor/ClassElementSorter.java @@ -0,0 +1,52 @@ +/* + * 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.ProgramClass; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.ClassVisitor; + +/** + * This ClassVisitor sorts the various elements of the classes that it visits: + * interfaces, constants, fields, methods, and attributes. + * + * @author Eric Lafortune + */ +public class ClassElementSorter +extends SimplifiedVisitor +implements ClassVisitor +{ + private final ClassVisitor interfaceSorter = new InterfaceSorter(); + private final ClassVisitor constantPoolSorter = new ConstantPoolSorter(); +// private ClassVisitor classMemberSorter = new ClassMemberSorter(); + private final ClassVisitor attributeSorter = new AttributeSorter(); + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + programClass.accept(constantPoolSorter); + programClass.accept(interfaceSorter); +// programClass.accept(classMemberSorter); + programClass.accept(attributeSorter); + } +} diff --git a/src/proguard/classfile/editor/ClassMemberSorter.java b/src/proguard/classfile/editor/ClassMemberSorter.java new file mode 100644 index 0000000..f31fcd0 --- /dev/null +++ b/src/proguard/classfile/editor/ClassMemberSorter.java @@ -0,0 +1,69 @@ +/* + * 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.visitor.ClassVisitor; + +import java.util.*; + +/** + * This ClassVisitor sorts the class members of the classes that it visits. + * The sorting order is based on the access flags, the names, and the + * descriptors. + * + * @author Eric Lafortune + */ +public class ClassMemberSorter implements ClassVisitor, Comparator +{ + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + // Sort the fields. + Arrays.sort(programClass.fields, 0, programClass.u2fieldsCount, this); + + // Sort the methods. + Arrays.sort(programClass.methods, 0, programClass.u2methodsCount, this); + } + + + public void visitLibraryClass(LibraryClass libraryClass) + { + } + + + // Implementations for Comparator. + + public int compare(Object object1, Object object2) + { + ProgramMember member1 = (ProgramMember)object1; + ProgramMember member2 = (ProgramMember)object2; + + return member1.u2accessFlags < member2.u2accessFlags ? -1 : + member1.u2accessFlags > member2.u2accessFlags ? 1 : + member1.u2nameIndex < member2.u2nameIndex ? -1 : + member1.u2nameIndex > member2.u2nameIndex ? 1 : + member1.u2descriptorIndex < member2.u2descriptorIndex ? -1 : + member1.u2descriptorIndex > member2.u2descriptorIndex ? 1 : + 0; + } +} diff --git a/src/proguard/classfile/editor/ClassReferenceFixer.java b/src/proguard/classfile/editor/ClassReferenceFixer.java new file mode 100644 index 0000000..9857903 --- /dev/null +++ b/src/proguard/classfile/editor/ClassReferenceFixer.java @@ -0,0 +1,546 @@ +/* + * 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; + } +} diff --git a/src/proguard/classfile/editor/CodeAttributeComposer.java b/src/proguard/classfile/editor/CodeAttributeComposer.java new file mode 100644 index 0000000..e783203 --- /dev/null +++ b/src/proguard/classfile/editor/CodeAttributeComposer.java @@ -0,0 +1,845 @@ +/* + * 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.preverification.*; +import proguard.classfile.attribute.preverification.visitor.*; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AttributeVisitor accumulates instructions and exceptions, and then + * copies them into code attributes that it visits. + * + * @author Eric Lafortune + */ +public class CodeAttributeComposer +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor, + ExceptionInfoVisitor, + StackMapFrameVisitor, + VerificationTypeVisitor, + LineNumberInfoVisitor, + LocalVariableInfoVisitor, + LocalVariableTypeInfoVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + public static boolean DEBUG = true; + //*/ + + + private static final int MAXIMUM_LEVELS = 32; + private static final int INVALID = -1; + + + private boolean allowExternalExceptionHandlers; + + private int maximumCodeLength; + private int codeLength; + private int exceptionTableLength; + private int level = -1; + + private byte[] code = new byte[ClassConstants.TYPICAL_CODE_LENGTH]; + private int[] oldInstructionOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH]; + + private final int[] codeFragmentOffsets = new int[MAXIMUM_LEVELS]; + private final int[] codeFragmentLengths = new int[MAXIMUM_LEVELS]; + private final int[][] instructionOffsetMap = new int[MAXIMUM_LEVELS][ClassConstants.TYPICAL_CODE_LENGTH + 1]; + + private ExceptionInfo[] exceptionTable = new ExceptionInfo[ClassConstants.TYPICAL_EXCEPTION_TABLE_LENGTH]; + + private int expectedStackMapFrameOffset; + + private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater(); + private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater(); +// private final InstructionWriter instructionWriter = new InstructionWriter(); + + + /** + * Creates a new CodeAttributeComposer that doesn't allow external exception + * handlers. + */ + public CodeAttributeComposer() + { + this(false); + } + + + /** + * Creates a new CodeAttributeComposer that optionally allows external + * exception handlers. + */ + public CodeAttributeComposer(boolean allowExternalExceptionHandlers) + { + this.allowExternalExceptionHandlers = allowExternalExceptionHandlers; + } + + + /** + * Starts a new code definition. + */ + public void reset() + { + maximumCodeLength = 0; + codeLength = 0; + exceptionTableLength = 0; + level = -1; + } + + + /** + * Starts a new code fragment. Branch instructions that are added are + * assumed to be relative within such code fragments. + * @param maximumCodeFragmentLength the maximum length of the code that will + * be added as part of this fragment. + */ + public void beginCodeFragment(int maximumCodeFragmentLength) + { + level++; + + if (level >= MAXIMUM_LEVELS) + { + throw new IllegalArgumentException("Maximum number of code fragment levels exceeded ["+level+"]"); + } + +// // TODO: Figure out some length. +// if (level == 0) +// { +// // Prepare for possible widening of instructions. +// instructionWriter.reset(2 * maximumCodeFragmentLength); +// } + + // Make sure there is sufficient space for adding the code fragment. + maximumCodeLength += maximumCodeFragmentLength; + + ensureCodeLength(maximumCodeLength); + + // Try to reuse the previous array for this code fragment. + if (instructionOffsetMap[level].length <= maximumCodeFragmentLength) + { + instructionOffsetMap[level] = new int[maximumCodeFragmentLength + 1]; + } + + // Initialize the offset map. + for (int index = 0; index <= maximumCodeFragmentLength; index++) + { + instructionOffsetMap[level][index] = INVALID; + } + + // Remember the location of the code fragment. + codeFragmentOffsets[level] = codeLength; + codeFragmentLengths[level] = maximumCodeFragmentLength; + } + + + /** + * Appends the given instruction with the given old offset. + * @param oldInstructionOffset the old offset of the instruction, to which + * branches and other references in the current + * code fragment are pointing. + * @param instruction the instruction to be appended. + */ + public void appendInstruction(int oldInstructionOffset, + Instruction instruction) + { + if (DEBUG) + { + println("["+codeLength+"] <- ", instruction.toString(oldInstructionOffset)); + } + + // Make sure the code array is large enough. + int newCodeLength = codeLength + instruction.length(codeLength); + + ensureCodeLength(newCodeLength); + + // Remember the old offset of the appended instruction. + oldInstructionOffsets[codeLength] = oldInstructionOffset; + + // Write the instruction. +// instruction.accept(null, +// null, +// new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null), +// codeLength, +// instructionWriter); + instruction.write(code, codeLength); + + // Fill out the new offset of the appended instruction. + instructionOffsetMap[level][oldInstructionOffset] = codeLength; + + // Continue appending at the next instruction offset. + codeLength = newCodeLength; + } + + + /** + * Appends the given label with the given old offset. + * @param oldInstructionOffset the old offset of the label, to which + * branches and other references in the current + * code fragment are pointing. + */ + public void appendLabel(int oldInstructionOffset) + { + if (DEBUG) + { + println("["+codeLength+"] <- ", "[" + oldInstructionOffset + "] (label)"); + } + + // Fill out the new offset of the appended instruction. + instructionOffsetMap[level][oldInstructionOffset] = codeLength; + } + + + /** + * Appends the given exception to the exception table. + * @param exceptionInfo the exception to be appended. + */ + public void appendException(ExceptionInfo exceptionInfo) + { + if (DEBUG) + { + print(" ", "Exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]"); + } + + // Remap the exception right away. + visitExceptionInfo(null, null, null, exceptionInfo); + + if (DEBUG) + { + System.out.println(" -> ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]"); + } + + // Don't add the exception if its instruction range is empty. + if (exceptionInfo.u2startPC == exceptionInfo.u2endPC) + { + if (DEBUG) + { + println(" ", " (not added because of empty instruction range)"); + } + + return; + } + + // Make sure there is sufficient space in the exception table. + if (exceptionTable.length <= exceptionTableLength) + { + ExceptionInfo[] newExceptionTable = new ExceptionInfo[exceptionTableLength+1]; + System.arraycopy(exceptionTable, 0, newExceptionTable, 0, exceptionTableLength); + exceptionTable = newExceptionTable; + } + + // Add the exception. + exceptionTable[exceptionTableLength++] = exceptionInfo; + } + + + /** + * Wraps up the current code fragment, continuing with the previous one on + * the stack. + */ + public void endCodeFragment() + { + if (level < 0) + { + throw new IllegalArgumentException("Code fragment not begun ["+level+"]"); + } + + // Remap the instructions of the code fragment. + int instructionOffset = codeFragmentOffsets[level]; + while (instructionOffset < codeLength) + { + // Get the next instruction. + Instruction instruction = InstructionFactory.create(code, instructionOffset); + + // Does this instruction still have to be remapped? + if (oldInstructionOffsets[instructionOffset] >= 0) + { + // Adapt the instruction for its new offset. + instruction.accept(null, null, null, instructionOffset, this); + + // Write the instruction back. +// instruction.accept(null, +// null, +// new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null), +// instructionOffset, +// instructionWriter); + instruction.write(code, instructionOffset); + + // Don't remap this instruction again. + oldInstructionOffsets[instructionOffset] = -1; + } + + // Continue remapping at the next instruction offset. + instructionOffset += instruction.length(instructionOffset); + } + + // Correct the estimated maximum code length, now that we know the + // actual length of this code fragment. + maximumCodeLength += codeLength - codeFragmentOffsets[level] - + codeFragmentLengths[level]; + + // Try to remap the exception handlers that couldn't be remapped before. + if (allowExternalExceptionHandlers) + { + for (int index = 0; index < exceptionTableLength; index++) + { + ExceptionInfo exceptionInfo = exceptionTable[index]; + + // Unmapped exception handlers are still negated. + int handlerPC = -exceptionInfo.u2handlerPC; + if (handlerPC > 0) + { + if (remappableInstructionOffset(handlerPC)) + { + exceptionInfo.u2handlerPC = remapInstructionOffset(handlerPC); + } + else if (level == 0) + { + throw new IllegalStateException("Couldn't remap exception handler offset ["+handlerPC+"]"); + } + } + } + } + + level--; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + if (DEBUG) + { + System.out.println("CodeAttributeComposer: putting results in ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]"); + } + + if (level != -1) + { + throw new IllegalArgumentException("Code fragment not ended ["+level+"]"); + } + + level++; + + // Make sure the code attribute has sufficient space for the composed + // code. + if (codeAttribute.u4codeLength < codeLength) + { + codeAttribute.code = new byte[codeLength]; + } + + // Copy the composed code over into the code attribute. + codeAttribute.u4codeLength = codeLength; + System.arraycopy(code, 0, codeAttribute.code, 0, codeLength); + + // Remove exceptions with empty code blocks (done before). + //exceptionTableLength = + // removeEmptyExceptions(exceptionTable, exceptionTableLength); + + // Make sure the exception table has sufficient space for the composed + // exceptions. + if (codeAttribute.exceptionTable.length < exceptionTableLength) + { + codeAttribute.exceptionTable = new ExceptionInfo[exceptionTableLength]; + } + + // Copy the exception table. + codeAttribute.u2exceptionTableLength = exceptionTableLength; + System.arraycopy(exceptionTable, 0, codeAttribute.exceptionTable, 0, exceptionTableLength); + + // Update the maximum stack size and local variable frame size. + stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); + variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); + + // Remap the line number table and the local variable table. + codeAttribute.attributesAccept(clazz, method, this); + + // Remap the exception table. + //codeAttribute.exceptionsAccept(clazz, method, this); + + // Remove exceptions with empty code blocks (done before). + //codeAttribute.u2exceptionTableLength = + // removeEmptyExceptions(codeAttribute.exceptionTable, + // codeAttribute.u2exceptionTableLength); + +// // Make sure instructions are widened if necessary. +// instructionWriter.visitCodeAttribute(clazz, method, codeAttribute); + + level--; + } + + + public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) + { + // Remap all stack map entries. + expectedStackMapFrameOffset = -1; + stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); + } + + + public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) + { + // Remap all stack map table entries. + expectedStackMapFrameOffset = 0; + stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); + } + + + public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) + { + // Remap all line number table entries. + lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this); + + // Remove line numbers with empty code blocks. + lineNumberTableAttribute.u2lineNumberTableLength = + removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable, + lineNumberTableAttribute.u2lineNumberTableLength, + codeAttribute.u4codeLength); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + // Remap all local variable table entries. + localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + + // Remove local variables with empty code blocks. + localVariableTableAttribute.u2localVariableTableLength = + removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable, + localVariableTableAttribute.u2localVariableTableLength, + codeAttribute.u2maxLocals); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + // Remap all local variable table entries. + localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + + // Remove local variables with empty code blocks. + localVariableTypeTableAttribute.u2localVariableTypeTableLength = + removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable, + localVariableTypeTableAttribute.u2localVariableTypeTableLength, + codeAttribute.u2maxLocals); + } + + + // Implementations for InstructionVisitor. + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + + public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) + { + // Adjust the branch offset. + branchInstruction.branchOffset = remapBranchOffset(offset, + branchInstruction.branchOffset); + } + + + public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) + { + // Adjust the default jump offset. + switchInstruction.defaultOffset = remapBranchOffset(offset, + switchInstruction.defaultOffset); + + // Adjust the jump offsets. + remapJumpOffsets(offset, + switchInstruction.jumpOffsets); + } + + + // Implementations for ExceptionInfoVisitor. + + public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) + { + // Remap the code offsets. Note that the instruction offset map also has + // an entry for the first offset after the code, for u2endPC. + exceptionInfo.u2startPC = remapInstructionOffset(exceptionInfo.u2startPC); + exceptionInfo.u2endPC = remapInstructionOffset(exceptionInfo.u2endPC); + + // See if we can remap the handler right away. Unmapped exception + // handlers are negated, in order to mark them as external. + int handlerPC = exceptionInfo.u2handlerPC; + exceptionInfo.u2handlerPC = + !allowExternalExceptionHandlers || + remappableInstructionOffset(handlerPC) ? + remapInstructionOffset(handlerPC) : + -handlerPC; + } + + + // Implementations for StackMapFrameVisitor. + + public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) + { + // Remap the stack map frame offset. + int stackMapFrameOffset = remapInstructionOffset(offset); + + int offsetDelta = stackMapFrameOffset; + + // Compute the offset delta if the frame is part of a stack map frame + // table (for JDK 6.0) instead of a stack map (for Java Micro Edition). + if (expectedStackMapFrameOffset >= 0) + { + offsetDelta -= expectedStackMapFrameOffset; + + expectedStackMapFrameOffset = stackMapFrameOffset + 1; + } + + stackMapFrame.u2offsetDelta = offsetDelta; + } + + + public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame) + { + // Remap the stack map frame offset. + visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame); + + // Remap the verification type offset. + sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this); + } + + + public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame) + { + // Remap the stack map frame offset. + visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame); + + // Remap the verification type offsets. + moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this); + } + + + public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame) + { + // Remap the stack map frame offset. + visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame); + + // Remap the verification type offsets. + fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this); + fullFrame.stackAccept(clazz, method, codeAttribute, offset, this); + } + + + // Implementations for VerificationTypeVisitor. + + public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {} + + + public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType) + { + // Remap the offset of the 'new' instruction. + uninitializedType.u2newInstructionOffset = remapInstructionOffset(uninitializedType.u2newInstructionOffset); + } + + + // Implementations for LineNumberInfoVisitor. + + public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) + { + // Remap the code offset. + lineNumberInfo.u2startPC = remapInstructionOffset(lineNumberInfo.u2startPC); + } + + + // Implementations for LocalVariableInfoVisitor. + + public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) + { + // Remap the code offset and length. + // TODO: The local variable frame might not be strictly preserved. + int startPC = remapInstructionOffset(localVariableInfo.u2startPC); + int endPC = remapInstructionOffset(localVariableInfo.u2startPC + localVariableInfo.u2length); + + localVariableInfo.u2startPC = startPC; + localVariableInfo.u2length = endPC - startPC; + } + + // Implementations for LocalVariableTypeInfoVisitor. + + public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) + { + // Remap the code offset and length. + // TODO: The local variable frame might not be strictly preserved. + int startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC); + int endPC = remapInstructionOffset(localVariableTypeInfo.u2startPC + localVariableTypeInfo.u2length); + + localVariableTypeInfo.u2startPC = startPC; + localVariableTypeInfo.u2length = endPC - startPC; + } + + + // Small utility methods. + + /** + * Make sure the code arrays have at least the given size. + */ + private void ensureCodeLength(int newCodeLength) + { + if (code.length < newCodeLength) + { + // Add 20% to avoid extending the arrays too often. + newCodeLength = newCodeLength * 6 / 5; + + byte[] newCode = new byte[newCodeLength]; + System.arraycopy(code, 0, newCode, 0, codeLength); + code = newCode; + + int[] newOldInstructionOffsets = new int[newCodeLength]; + System.arraycopy(oldInstructionOffsets, 0, newOldInstructionOffsets, 0, codeLength); + oldInstructionOffsets = newOldInstructionOffsets; + } + } + + + /** + * Adjusts the given jump offsets for the instruction at the given offset. + */ + private void remapJumpOffsets(int offset, int[] jumpOffsets) + { + for (int index = 0; index < jumpOffsets.length; index++) + { + jumpOffsets[index] = remapBranchOffset(offset, jumpOffsets[index]); + } + } + + + /** + * Computes the new branch offset for the instruction at the given new offset + * with the given old branch offset. + */ + private int remapBranchOffset(int newInstructionOffset, int branchOffset) + { + if (newInstructionOffset < 0 || + newInstructionOffset > codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+newInstructionOffset +"] in code with length ["+codeLength+"]"); + } + + int oldInstructionOffset = oldInstructionOffsets[newInstructionOffset]; + + return remapInstructionOffset(oldInstructionOffset + branchOffset) - + remapInstructionOffset(oldInstructionOffset); + } + + + /** + * Computes the new instruction offset for the instruction at the given old + * offset. + */ + private int remapInstructionOffset(int oldInstructionOffset) + { + if (oldInstructionOffset < 0 || + oldInstructionOffset > codeFragmentLengths[level]) + { + throw new IllegalArgumentException("Instruction offset ["+oldInstructionOffset +"] out of range in code fragment with length ["+codeFragmentLengths[level]+"] at level "+level); + } + + int newInstructionOffset = instructionOffsetMap[level][oldInstructionOffset]; + if (newInstructionOffset == INVALID) + { + throw new IllegalArgumentException("Invalid instruction offset ["+oldInstructionOffset +"] in code fragment at level "+level); + } + + return newInstructionOffset; + } + + + /** + * Returns whether the given old instruction offset can be remapped at the + */ + private boolean remappableInstructionOffset(int oldInstructionOffset) + { + return + oldInstructionOffset <= codeFragmentLengths[level] && + instructionOffsetMap[level][oldInstructionOffset] > INVALID; + } + + + /** + * Returns the given list of exceptions, without the ones that have empty + * code blocks. + */ + private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos, + int exceptionInfoCount) + { + // Overwrite all empty exceptions. + int newIndex = 0; + for (int index = 0; index < exceptionInfoCount; index++) + { + ExceptionInfo exceptionInfo = exceptionInfos[index]; + if (exceptionInfo.u2startPC < exceptionInfo.u2endPC) + { + exceptionInfos[newIndex++] = exceptionInfo; + } + } + + // Clear the unused array entries. + for (int index = newIndex; index < exceptionInfoCount; index++) + { + exceptionInfos[index] = null; + } + + return newIndex; + } + + + /** + * Returns the given list of line numbers, without the ones that have empty + * code blocks or that exceed the code size. + */ + private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos, + int lineNumberInfoCount, + int codeLength) + { + // Overwrite all empty line number entries. + int newIndex = 0; + for (int index = 0; index < lineNumberInfoCount; index++) + { + LineNumberInfo lineNumberInfo = lineNumberInfos[index]; + int startPC = lineNumberInfo.u2startPC; + if (startPC < codeLength && + (index == 0 || startPC > lineNumberInfos[index-1].u2startPC)) + { + lineNumberInfos[newIndex++] = lineNumberInfo; + } + } + + // Clear the unused array entries. + for (int index = newIndex; index < lineNumberInfoCount; index++) + { + lineNumberInfos[index] = null; + } + + return newIndex; + } + + + /** + * Returns the given list of local variables, without the ones that have empty + * code blocks or that exceed the actual number of local variables. + */ + private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos, + int localVariableInfoCount, + int maxLocals) + { + // Overwrite all empty local variable entries. + int newIndex = 0; + for (int index = 0; index < localVariableInfoCount; index++) + { + LocalVariableInfo localVariableInfo = localVariableInfos[index]; + if (localVariableInfo.u2length > 0 && + localVariableInfo.u2index < maxLocals) + { + localVariableInfos[newIndex++] = localVariableInfo; + } + } + + // Clear the unused array entries. + for (int index = newIndex; index < localVariableInfoCount; index++) + { + localVariableInfos[index] = null; + } + + return newIndex; + } + + + /** + * Returns the given list of local variable types, without the ones that + * have empty code blocks or that exceed the actual number of local variables. + */ + private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos, + int localVariableTypeInfoCount, + int maxLocals) + { + // Overwrite all empty local variable type entries. + int newIndex = 0; + for (int index = 0; index < localVariableTypeInfoCount; index++) + { + LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index]; + if (localVariableTypeInfo.u2length > 0 && + localVariableTypeInfo.u2index < maxLocals) + { + localVariableTypeInfos[newIndex++] = localVariableTypeInfo; + } + } + + // Clear the unused array entries. + for (int index = newIndex; index < localVariableTypeInfoCount; index++) + { + localVariableTypeInfos[index] = null; + } + + return newIndex; + } + + + private void println(String string1, String string2) + { + print(string1, string2); + + System.out.println(); + } + + private void print(String string1, String string2) + { + System.out.print(string1); + + for (int index = 0; index < level; index++) + { + System.out.print(" "); + } + + System.out.print(string2); + } + + + public static void main(String[] args) + { + CodeAttributeComposer composer = new CodeAttributeComposer(); + + composer.beginCodeFragment(4); + composer.appendInstruction(0, new SimpleInstruction(InstructionConstants.OP_ICONST_0)); + composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ISTORE, 0)); + composer.appendInstruction(2, new BranchInstruction(InstructionConstants.OP_GOTO, 1)); + + composer.beginCodeFragment(4); + composer.appendInstruction(0, new VariableInstruction(InstructionConstants.OP_IINC, 0, 1)); + composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ILOAD, 0)); + composer.appendInstruction(2, new SimpleInstruction(InstructionConstants.OP_ICONST_5)); + composer.appendInstruction(3, new BranchInstruction(InstructionConstants.OP_IFICMPLT, -3)); + composer.endCodeFragment(); + + composer.appendInstruction(3, new SimpleInstruction(InstructionConstants.OP_RETURN)); + composer.endCodeFragment(); + } +} diff --git a/src/proguard/classfile/editor/CodeAttributeEditor.java b/src/proguard/classfile/editor/CodeAttributeEditor.java new file mode 100644 index 0000000..9658c98 --- /dev/null +++ b/src/proguard/classfile/editor/CodeAttributeEditor.java @@ -0,0 +1,1163 @@ +/* + * 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.preverification.*; +import proguard.classfile.attribute.preverification.visitor.*; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.ClassPrinter; + +/** + * This AttributeVisitor accumulates specified changes to code, and then applies + * these accumulated changes to the code attributes that it visits. + * + * @author Eric Lafortune + */ +public class CodeAttributeEditor +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor, + ExceptionInfoVisitor, + StackMapFrameVisitor, + VerificationTypeVisitor, + LineNumberInfoVisitor, + LocalVariableInfoVisitor, + LocalVariableTypeInfoVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = true; + //*/ + + private boolean updateFrameSizes; + + private int codeLength; + private boolean modified; + private boolean simple; + + /*private*/public Instruction[] preInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; + /*private*/public Instruction[] replacements = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; + /*private*/public Instruction[] postInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH]; + /*private*/public boolean[] deleted = new boolean[ClassConstants.TYPICAL_CODE_LENGTH]; + + private int[] instructionOffsetMap = new int[ClassConstants.TYPICAL_CODE_LENGTH]; + private int newOffset; + private boolean lengthIncreased; + + private int expectedStackMapFrameOffset; + + private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater(); + private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater(); + private final InstructionWriter instructionWriter = new InstructionWriter(); + + + public CodeAttributeEditor() + { + this(true); + } + + + public CodeAttributeEditor(boolean updateFrameSizes) + { + this.updateFrameSizes = updateFrameSizes; + } + + + /** + * Resets the accumulated code changes. + * @param codeLength the length of the code that will be edited next. + */ + public void reset(int codeLength) + { + this.codeLength = codeLength; + + // Try to reuse the previous arrays. + if (preInsertions.length < codeLength) + { + preInsertions = new Instruction[codeLength]; + replacements = new Instruction[codeLength]; + postInsertions = new Instruction[codeLength]; + deleted = new boolean[codeLength]; + } + else + { + for (int index = 0; index < codeLength; index++) + { + preInsertions[index] = null; + replacements[index] = null; + postInsertions[index] = null; + deleted[index] = false; + } + } + + modified = false; + simple = true; + + } + + + /** + * Remembers to place the given instruction right before the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instruction the new instruction. + */ + public void insertBeforeInstruction(int instructionOffset, Instruction instruction) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + preInsertions[instructionOffset] = instruction; + + modified = true; + simple = false; + + } + + + /** + * Remembers to place the given instructions right before the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instructions the new instructions. + */ + public void insertBeforeInstruction(int instructionOffset, Instruction[] instructions) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + preInsertions[instructionOffset] = new CompositeInstruction(instructions); + + modified = true; + simple = false; + + } + + + /** + * Remembers to replace the instruction at the given offset by the given + * instruction. + * @param instructionOffset the offset of the instruction to be replaced. + * @param instruction the new instruction. + */ + public void replaceInstruction(int instructionOffset, Instruction instruction) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + replacements[instructionOffset] = instruction; + + modified = true; + } + + + /** + * Remembers to replace the instruction at the given offset by the given + * instructions. + * @param instructionOffset the offset of the instruction to be replaced. + * @param instructions the new instructions. + */ + public void replaceInstruction(int instructionOffset, Instruction[] instructions) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + replacements[instructionOffset] = new CompositeInstruction(instructions); + + modified = true; + } + + + /** + * Remembers to place the given instruction right after the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instruction the new instruction. + */ + public void insertAfterInstruction(int instructionOffset, Instruction instruction) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + postInsertions[instructionOffset] = instruction; + + modified = true; + simple = false; + } + + + /** + * Remembers to place the given instructions right after the instruction + * at the given offset. + * @param instructionOffset the offset of the instruction. + * @param instructions the new instructions. + */ + public void insertAfterInstruction(int instructionOffset, Instruction[] instructions) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + postInsertions[instructionOffset] = new CompositeInstruction(instructions); + + modified = true; + simple = false; + } + + + /** + * Remembers to delete the instruction at the given offset. + * @param instructionOffset the offset of the instruction to be deleted. + */ + public void deleteInstruction(int instructionOffset) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + deleted[instructionOffset] = true; + + modified = true; + simple = false; + } + + + /** + * Remembers not to delete the instruction at the given offset. + * @param instructionOffset the offset of the instruction not to be deleted. + */ + public void undeleteInstruction(int instructionOffset) + { + if (instructionOffset < 0 || + instructionOffset >= codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]"); + } + + deleted[instructionOffset] = false; + } + + + /** + * Returns whether the instruction at the given offset has been modified + * in any way. + */ + public boolean isModified(int instructionOffset) + { + return preInsertions[instructionOffset] != null || + replacements[instructionOffset] != null || + postInsertions[instructionOffset] != null || + deleted[instructionOffset]; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { +// DEBUG = +// clazz.getName().equals("abc/Def") && +// method.getName(clazz).equals("abc"); + + // TODO: Remove this when the code has stabilized. + // Catch any unexpected exceptions from the actual visiting method. + try + { + // Process the code. + visitCodeAttribute0(clazz, method, codeAttribute); + } + catch (RuntimeException ex) + { + System.err.println("Unexpected error while editing code:"); + System.err.println(" Class = ["+clazz.getName()+"]"); + System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); + System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); + + throw ex; + } + } + + + public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + if (DEBUG) + { + System.out.println("CodeAttributeEditor: ["+clazz.getName()+"."+method.getName(clazz)+"]"); + } + + // Avoid doing any work if nothing is changing anyway. + if (!modified) + { + return; + } + + // Check if we can perform a faster simple replacement of instructions. + if (canPerformSimpleReplacements(codeAttribute)) + { + // Simply overwrite the instructions. + performSimpleReplacements(codeAttribute); + + // Update the maximum stack size and local variable frame size. + updateFrameSizes(clazz, method, codeAttribute); + } + else + { + // Move and remap the instructions. + codeAttribute.u4codeLength = + updateInstructions(clazz, method, codeAttribute); + + // Remap the exception table. + codeAttribute.exceptionsAccept(clazz, method, this); + + // Remove exceptions with empty code blocks. + codeAttribute.u2exceptionTableLength = + removeEmptyExceptions(codeAttribute.exceptionTable, + codeAttribute.u2exceptionTableLength); + + // Update the maximum stack size and local variable frame size. + updateFrameSizes(clazz, method, codeAttribute); + + // Remap the line number table and the local variable table. + codeAttribute.attributesAccept(clazz, method, this); + + // Make sure instructions are widened if necessary. + instructionWriter.visitCodeAttribute(clazz, method, codeAttribute); + } + } + + + private void updateFrameSizes(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + if (updateFrameSizes) + { + stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); + variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute); + } + } + + + public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) + { + // Remap all stack map entries. + expectedStackMapFrameOffset = -1; + stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); + } + + + public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) + { + // Remap all stack map table entries. + expectedStackMapFrameOffset = 0; + stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); + } + + + public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) + { + // Remap all line number table entries. + lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this); + + // Remove line numbers with empty code blocks. + lineNumberTableAttribute.u2lineNumberTableLength = + removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable, + lineNumberTableAttribute.u2lineNumberTableLength, + codeAttribute.u4codeLength); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + // Remap all local variable table entries. + localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + + // Remove local variables with empty code blocks. + localVariableTableAttribute.u2localVariableTableLength = + removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable, + localVariableTableAttribute.u2localVariableTableLength, + codeAttribute.u2maxLocals); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + // Remap all local variable table entries. + localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + + // Remove local variables with empty code blocks. + localVariableTypeTableAttribute.u2localVariableTypeTableLength = + removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable, + localVariableTypeTableAttribute.u2localVariableTypeTableLength, + codeAttribute.u2maxLocals); + } + + + /** + * Checks if it is possible to modifies the given code without having to + * update any offsets. + * @param codeAttribute the code to be changed. + * @return the new code length. + */ + private boolean canPerformSimpleReplacements(CodeAttribute codeAttribute) + { + if (!simple) + { + return false; + } + + byte[] code = codeAttribute.code; + int codeLength = codeAttribute.u4codeLength; + + // Go over all replacement instructions. + for (int offset = 0; offset < codeLength; offset++) + { + // Check if the replacement instruction, if any, has a different + // length than the original instruction. + Instruction replacementInstruction = replacements[offset]; + if (replacementInstruction != null && + replacementInstruction.length(offset) != + InstructionFactory.create(code, offset).length(offset)) + { + return false; + } + } + + return true; + } + + + /** + * Modifies the given code without updating any offsets. + * @param codeAttribute the code to be changed. + */ + private void performSimpleReplacements(CodeAttribute codeAttribute) + { + int codeLength = codeAttribute.u4codeLength; + + // Go over all replacement instructions. + for (int offset = 0; offset < codeLength; offset++) + { + // Overwrite the original instruction with the replacement + // instruction if any. + Instruction replacementInstruction = replacements[offset]; + if (replacementInstruction != null) + { + replacementInstruction.write(codeAttribute, offset); + + if (DEBUG) + { + System.out.println(" Replaced "+replacementInstruction.toString(newOffset)); + } + } + } + } + + + /** + * Modifies the given code based on the previously specified changes. + * @param clazz the class file of the code to be changed. + * @param method the method of the code to be changed. + * @param codeAttribute the code to be changed. + * @return the new code length. + */ + private int updateInstructions(Clazz clazz, + Method method, + CodeAttribute codeAttribute) + { + byte[] oldCode = codeAttribute.code; + int oldLength = codeAttribute.u4codeLength; + + // Make sure there is a sufficiently large instruction offset map. + if (instructionOffsetMap.length < oldLength + 1) + { + instructionOffsetMap = new int[oldLength + 1]; + } + + // Fill out the instruction offset map. + int newLength = mapInstructions(oldCode, + oldLength); + + // Create a new code array if necessary. + if (lengthIncreased) + { + codeAttribute.code = new byte[newLength]; + } + + // Prepare for possible widening of instructions. + instructionWriter.reset(newLength); + + // Move the instructions into the new code array. + moveInstructions(clazz, + method, + codeAttribute, + oldCode, + oldLength); + + // We can return the new length. + return newLength; + } + + + /** + * Fills out the instruction offset map for the given code block. + * @param oldCode the instructions to be moved. + * @param oldLength the code length. + * @return the new code length. + */ + private int mapInstructions(byte[] oldCode, int oldLength) + { + // Start mapping instructions at the beginning. + newOffset = 0; + lengthIncreased = false; + + int oldOffset = 0; + do + { + // Get the next instruction. + Instruction instruction = InstructionFactory.create(oldCode, oldOffset); + + // Compute the mapping of the instruction. + mapInstruction(oldOffset, instruction); + + oldOffset += instruction.length(oldOffset); + + if (newOffset > oldOffset) + { + lengthIncreased = true; + } + } + while (oldOffset < oldLength); + + // Also add an entry for the first offset after the code. + instructionOffsetMap[oldOffset] = newOffset; + + return newOffset; + } + + + /** + * Fills out the instruction offset map for the given instruction. + * @param oldOffset the instruction's old offset. + * @param instruction the instruction to be moved. + */ + private void mapInstruction(int oldOffset, + Instruction instruction) + { + instructionOffsetMap[oldOffset] = newOffset; + + // Account for the pre-inserted instruction, if any. + Instruction preInstruction = preInsertions[oldOffset]; + if (preInstruction != null) + { + newOffset += preInstruction.length(newOffset); + } + + // Account for the replacement instruction, or for the current + // instruction, if it shouldn't be deleted. + Instruction replacementInstruction = replacements[oldOffset]; + if (replacementInstruction != null) + { + newOffset += replacementInstruction.length(newOffset); + } + else if (!deleted[oldOffset]) + { + // Note that the instruction's length may change at its new offset, + // e.g. if it is a switch instruction. + newOffset += instruction.length(newOffset); + } + + // Account for the post-inserted instruction, if any. + Instruction postInstruction = postInsertions[oldOffset]; + if (postInstruction != null) + { + newOffset += postInstruction.length(newOffset); + } + } + + + /** + * Moves the given code block to the new offsets. + * @param clazz the class file of the code to be changed. + * @param method the method of the code to be changed. + * @param codeAttribute the code to be changed. + * @param oldCode the original code to be moved. + * @param oldLength the original code length. + */ + private void moveInstructions(Clazz clazz, + Method method, + CodeAttribute codeAttribute, + byte[] oldCode, + int oldLength) + { + // Start writing instructions at the beginning. + newOffset = 0; + + int oldOffset = 0; + do + { + // Get the next instruction. + Instruction instruction = InstructionFactory.create(oldCode, oldOffset); + + // Move the instruction to its new offset. + moveInstruction(clazz, + method, + codeAttribute, + oldOffset, + instruction); + + oldOffset += instruction.length(oldOffset); + } + while (oldOffset < oldLength); + } + + + /** + * Moves the given instruction to its new offset. + * @param clazz the class file of the code to be changed. + * @param method the method of the code to be changed. + * @param codeAttribute the code to be changed. + * @param oldOffset the original instruction offset. + * @param instruction the original instruction. + */ + private void moveInstruction(Clazz clazz, + Method method, + CodeAttribute codeAttribute, + int oldOffset, + Instruction instruction) + { + // Remap and insert the pre-inserted instruction, if any. + Instruction preInstruction = preInsertions[oldOffset]; + if (preInstruction != null) + { + if (DEBUG) + { + System.out.println(" Pre-inserted "+preInstruction.toString(newOffset)); + } + + // Remap the instruction. + preInstruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + + // Remap and insert the replacement instruction, or the current + // instruction, if it shouldn't be deleted. + Instruction replacementInstruction = replacements[oldOffset]; + if (replacementInstruction != null) + { + if (DEBUG) + { + System.out.println(" Replaced "+replacementInstruction.toString(newOffset)); + } + // Remap the instruction. + replacementInstruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + else if (!deleted[oldOffset]) + { + if (DEBUG) + { + System.out.println(" Copied "+instruction.toString(newOffset)); + } + + // Remap the instruction. + instruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + + // Remap and insert the post-inserted instruction, if any. + Instruction postInstruction = postInsertions[oldOffset]; + if (postInstruction != null) + { + if (DEBUG) + { + System.out.println(" Post-inserted "+postInstruction.toString(newOffset)); + } + + // Remap the instruction. + postInstruction.accept(clazz, method, codeAttribute, oldOffset, this); + } + } + + + // Implementations for InstructionVisitor. + + public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) + { + // Write out the instruction. + instructionWriter.visitSimpleInstruction(clazz, + method, + codeAttribute, + newOffset, + simpleInstruction); + + newOffset += simpleInstruction.length(newOffset); + } + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + // Write out the instruction. + instructionWriter.visitConstantInstruction(clazz, + method, + codeAttribute, + newOffset, + constantInstruction); + + newOffset += constantInstruction.length(newOffset); + } + + + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) + { + // Write out the instruction. + instructionWriter.visitVariableInstruction(clazz, + method, + codeAttribute, + newOffset, + variableInstruction); + + newOffset += variableInstruction.length(newOffset); + } + + + public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) + { + // Adjust the branch offset. + branchInstruction.branchOffset = remapBranchOffset(offset, + branchInstruction.branchOffset); + + // Write out the instruction. + instructionWriter.visitBranchInstruction(clazz, + method, + codeAttribute, + newOffset, + branchInstruction); + + newOffset += branchInstruction.length(newOffset); + } + + + public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) + { + // Adjust the default jump offset. + tableSwitchInstruction.defaultOffset = remapBranchOffset(offset, + tableSwitchInstruction.defaultOffset); + + // Adjust the jump offsets. + remapJumpOffsets(offset, + tableSwitchInstruction.jumpOffsets); + + // Write out the instruction. + instructionWriter.visitTableSwitchInstruction(clazz, + method, + codeAttribute, + newOffset, + tableSwitchInstruction); + + newOffset += tableSwitchInstruction.length(newOffset); + } + + + public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) + { + // Adjust the default jump offset. + lookUpSwitchInstruction.defaultOffset = remapBranchOffset(offset, + lookUpSwitchInstruction.defaultOffset); + + // Adjust the jump offsets. + remapJumpOffsets(offset, + lookUpSwitchInstruction.jumpOffsets); + + // Write out the instruction. + instructionWriter.visitLookUpSwitchInstruction(clazz, + method, + codeAttribute, + newOffset, + lookUpSwitchInstruction); + + newOffset += lookUpSwitchInstruction.length(newOffset); + } + + + // Implementations for ExceptionInfoVisitor. + + public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) + { + // Remap the code offsets. Note that the instruction offset map also has + // an entry for the first offset after the code, for u2endPC. + exceptionInfo.u2startPC = remapInstructionOffset(exceptionInfo.u2startPC); + exceptionInfo.u2endPC = remapInstructionOffset(exceptionInfo.u2endPC); + exceptionInfo.u2handlerPC = remapInstructionOffset(exceptionInfo.u2handlerPC); + } + + + // Implementations for StackMapFrameVisitor. + + public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) + { + // Remap the stack map frame offset. + int stackMapFrameOffset = remapInstructionOffset(offset); + + int offsetDelta = stackMapFrameOffset; + + // Compute the offset delta if the frame is part of a stack map frame + // table (for JDK 6.0) instead of a stack map (for Java Micro Edition). + if (expectedStackMapFrameOffset >= 0) + { + offsetDelta -= expectedStackMapFrameOffset; + + expectedStackMapFrameOffset = stackMapFrameOffset + 1; + } + + stackMapFrame.u2offsetDelta = offsetDelta; + } + + + public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame) + { + // Remap the stack map frame offset. + visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame); + + // Remap the verification type offset. + sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this); + } + + + public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame) + { + // Remap the stack map frame offset. + visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame); + + // Remap the verification type offsets. + moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this); + } + + + public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame) + { + // Remap the stack map frame offset. + visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame); + + // Remap the verification type offsets. + fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this); + fullFrame.stackAccept(clazz, method, codeAttribute, offset, this); + } + + + // Implementations for VerificationTypeVisitor. + + public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {} + + + public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType) + { + // Remap the offset of the 'new' instruction. + uninitializedType.u2newInstructionOffset = remapInstructionOffset(uninitializedType.u2newInstructionOffset); + } + + + // Implementations for LineNumberInfoVisitor. + + public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) + { + // Remap the code offset. + lineNumberInfo.u2startPC = remapInstructionOffset(lineNumberInfo.u2startPC); + } + + + // Implementations for LocalVariableInfoVisitor. + + public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) + { + // Remap the code offset and length. + // TODO: The local variable frame might not be strictly preserved. + localVariableInfo.u2length = remapBranchOffset(localVariableInfo.u2startPC, + localVariableInfo.u2length); + localVariableInfo.u2startPC = remapInstructionOffset(localVariableInfo.u2startPC); + } + + + // Implementations for LocalVariableTypeInfoVisitor. + + public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) + { + // Remap the code offset and length. + // TODO: The local variable frame might not be strictly preserved. + localVariableTypeInfo.u2length = remapBranchOffset(localVariableTypeInfo.u2startPC, + localVariableTypeInfo.u2length); + localVariableTypeInfo.u2startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC); + } + + + // Small utility methods. + + /** + * Adjusts the given jump offsets for the instruction at the given offset. + */ + private void remapJumpOffsets(int offset, int[] jumpOffsets) + { + for (int index = 0; index < jumpOffsets.length; index++) + { + jumpOffsets[index] = remapBranchOffset(offset, jumpOffsets[index]); + } + } + + + /** + * Computes the new branch offset for the instruction at the given offset + * with the given branch offset. + */ + private int remapBranchOffset(int offset, int branchOffset) + { + return remapInstructionOffset(offset + branchOffset) - newOffset; + } + + + /** + * Computes the new instruction offset for the instruction at the given offset. + */ + private int remapInstructionOffset(int offset) + { + if (offset < 0 || + offset > codeLength) + { + throw new IllegalArgumentException("Invalid instruction offset ["+offset+"] in code with length ["+codeLength+"]"); + } + + return instructionOffsetMap[offset]; + } + + + /** + * Returns the given list of exceptions, without the ones that have empty + * code blocks. + */ + private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos, + int exceptionInfoCount) + { + // Overwrite all empty exceptions. + int newIndex = 0; + for (int index = 0; index < exceptionInfoCount; index++) + { + ExceptionInfo exceptionInfo = exceptionInfos[index]; + if (exceptionInfo.u2startPC < exceptionInfo.u2endPC) + { + exceptionInfos[newIndex++] = exceptionInfo; + } + } + + return newIndex; + } + + + /** + * Returns the given list of line numbers, without the ones that have empty + * code blocks or that exceed the code size. + */ + private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos, + int lineNumberInfoCount, + int codeLength) + { + // Overwrite all empty line number entries. + int newIndex = 0; + for (int index = 0; index < lineNumberInfoCount; index++) + { + LineNumberInfo lineNumberInfo = lineNumberInfos[index]; + int startPC = lineNumberInfo.u2startPC; + if (startPC < codeLength && + (index == 0 || startPC > lineNumberInfos[index-1].u2startPC)) + { + lineNumberInfos[newIndex++] = lineNumberInfo; + } + } + + return newIndex; + } + + + /** + * Returns the given list of local variables, without the ones that have empty + * code blocks or that exceed the actual number of local variables. + */ + private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos, + int localVariableInfoCount, + int maxLocals) + { + // Overwrite all empty local variable entries. + int newIndex = 0; + for (int index = 0; index < localVariableInfoCount; index++) + { + LocalVariableInfo localVariableInfo = localVariableInfos[index]; + if (localVariableInfo.u2length > 0 && + localVariableInfo.u2index < maxLocals) + { + localVariableInfos[newIndex++] = localVariableInfo; + } + } + + return newIndex; + } + + + /** + * Returns the given list of local variable types, without the ones that + * have empty code blocks or that exceed the actual number of local variables. + */ + private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos, + int localVariableTypeInfoCount, + int maxLocals) + { + // Overwrite all empty local variable type entries. + int newIndex = 0; + for (int index = 0; index < localVariableTypeInfoCount; index++) + { + LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index]; + if (localVariableTypeInfo.u2length > 0 && + localVariableTypeInfo.u2index < maxLocals) + { + localVariableTypeInfos[newIndex++] = localVariableTypeInfo; + } + } + + return newIndex; + } + + + private class CompositeInstruction + extends Instruction + { + private Instruction[] instructions; + + + private CompositeInstruction(Instruction[] instructions) + { + this.instructions = instructions; + } + + + // Implementations for Instruction. + + public Instruction shrink() + { + for (int index = 0; index < instructions.length; index++) + { + instructions[index] = instructions[index].shrink(); + } + + return this; + } + + + public void write(byte[] code, int offset) + { + for (int index = 0; index < instructions.length; index++) + { + Instruction instruction = instructions[index]; + + instruction.write(code, offset); + + offset += instruction.length(offset); + } + } + + + protected void readInfo(byte[] code, int offset) + { + throw new UnsupportedOperationException("Can't read composite instruction"); + } + + + protected void writeInfo(byte[] code, int offset) + { + throw new UnsupportedOperationException("Can't write composite instruction"); + } + + + public int length(int offset) + { + int newOffset = offset; + + for (int index = 0; index < instructions.length; index++) + { + newOffset += instructions[index].length(newOffset); + } + + return newOffset - offset; + } + + + public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor) + { + if (instructionVisitor != CodeAttributeEditor.this) + { + throw new UnsupportedOperationException("Unexpected visitor ["+instructionVisitor+"]"); + } + + for (int index = 0; index < instructions.length; index++) + { + Instruction instruction = instructions[index]; + + instruction.accept(clazz, method, codeAttribute, offset, CodeAttributeEditor.this); + + offset += instruction.length(offset); + } + } + + + // Implementations for Object. + + public String toString() + { + StringBuffer stringBuffer = new StringBuffer(); + + for (int index = 0; index < instructions.length; index++) + { + stringBuffer.append(instructions[index].toString()).append("; "); + } + + return stringBuffer.toString(); + } + } +} diff --git a/src/proguard/classfile/editor/CodeAttributeEditorResetter.java b/src/proguard/classfile/editor/CodeAttributeEditorResetter.java new file mode 100644 index 0000000..9962ea5 --- /dev/null +++ b/src/proguard/classfile/editor/CodeAttributeEditorResetter.java @@ -0,0 +1,60 @@ +/* + * 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.visitor.AttributeVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AttributeVisitor resets it CodeAttributeEditor whenever it visits a + * code attribute. + * + * @author Eric Lafortune + */ +public class CodeAttributeEditorResetter +extends SimplifiedVisitor +implements AttributeVisitor +{ + private final CodeAttributeEditor codeAttributeEditor; + + + /** + * Creates a new CodeAttributeEditorResetter. + * @param codeAttributeEditor the code attribute editor that will be reset. + */ + public CodeAttributeEditorResetter(CodeAttributeEditor codeAttributeEditor) + { + this.codeAttributeEditor = codeAttributeEditor; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + codeAttributeEditor.reset(codeAttribute.u4codeLength); + } +} diff --git a/src/proguard/classfile/editor/ComparableConstant.java b/src/proguard/classfile/editor/ComparableConstant.java new file mode 100644 index 0000000..bb81221 --- /dev/null +++ b/src/proguard/classfile/editor/ComparableConstant.java @@ -0,0 +1,200 @@ +/* + * 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.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.util.SimplifiedVisitor; + + +/** + * This class is a Comparable wrapper of Constant + * objects. It can store an index, in order to identify the constant pool + * entry after it has been sorted. The comparison is primarily based on the + * types of the constant pool entries, and secondarily on the contents of + * the constant pool entries. + * + * @author Eric Lafortune + */ +class ComparableConstant +extends SimplifiedVisitor +implements Comparable, ConstantVisitor +{ + private static final int[] PRIORITIES = new int[13]; + static + { + PRIORITIES[ClassConstants.CONSTANT_Integer] = 0; // Possibly byte index (ldc). + PRIORITIES[ClassConstants.CONSTANT_Float] = 1; + PRIORITIES[ClassConstants.CONSTANT_String] = 2; + PRIORITIES[ClassConstants.CONSTANT_Class] = 3; + PRIORITIES[ClassConstants.CONSTANT_Long] = 4; // Always wide index (ldc2_w). + PRIORITIES[ClassConstants.CONSTANT_Double] = 5; + PRIORITIES[ClassConstants.CONSTANT_Fieldref] = 6; // Always wide index. + PRIORITIES[ClassConstants.CONSTANT_Methodref] = 7; + PRIORITIES[ClassConstants.CONSTANT_InterfaceMethodref] = 8; + PRIORITIES[ClassConstants.CONSTANT_NameAndType] = 9; + PRIORITIES[ClassConstants.CONSTANT_Utf8] = 10; + } + + private final Clazz clazz; + private final int thisIndex; + private final Constant thisConstant; + + private Constant otherConstant; + private int result; + + + public ComparableConstant(Clazz clazz, int index, Constant constant) + { + this.clazz = clazz; + this.thisIndex = index; + this.thisConstant = constant; + } + + + public int getIndex() + { + return thisIndex; + } + + + public Constant getConstant() + { + return thisConstant; + } + + + // Implementations for Comparable. + + public int compareTo(Object other) + { + ComparableConstant otherComparableConstant = (ComparableConstant)other; + + otherConstant = otherComparableConstant.thisConstant; + + // Compare based on the original indices, if the actual constant pool + // entries are the same. + if (thisConstant == otherConstant) + { + int otherIndex = otherComparableConstant.thisIndex; + + return thisIndex < otherIndex ? -1 : + thisIndex == otherIndex ? 0 : + 1; + } + + // Compare based on the tags, if they are different. + int thisTag = thisConstant.getTag(); + int otherTag = otherConstant.getTag(); + + if (thisTag != otherTag) + { + return PRIORITIES[thisTag] < PRIORITIES[otherTag] ? -1 : 1; + } + + // Otherwise compare based on the contents of the Constant objects. + thisConstant.accept(clazz, this); + + return result; + } + + + // Implementations for ConstantVisitor. + + public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) + { + // In JDK 1.4, we can use Integer.compare(a,b). + result = new Integer(integerConstant.getValue()).compareTo(new Integer(((IntegerConstant)otherConstant).getValue())); + } + + public void visitLongConstant(Clazz clazz, LongConstant longConstant) + { + // In JDK 1.4, we can use Long.compare(a,b). + result = new Long(longConstant.getValue()).compareTo(new Long(((LongConstant)otherConstant).getValue())); + } + + public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) + { + // In JDK 1.4, we can use Float.compare(a,b). + result = new Float(floatConstant.getValue()).compareTo(new Float(((FloatConstant)otherConstant).getValue())); + } + + public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) + { + // In JDK 1.4, we can use Double.compare(a,b). + result = new Double(doubleConstant.getValue()).compareTo(new Double(((DoubleConstant)otherConstant).getValue())); + } + + public void visitStringConstant(Clazz clazz, StringConstant stringConstant) + { + result = stringConstant.getString(clazz).compareTo(((StringConstant)otherConstant).getString(clazz)); + } + + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) + { + result = utf8Constant.getString().compareTo(((Utf8Constant)otherConstant).getString()); + } + + public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant) + { + RefConstant otherRefConstant = (RefConstant)otherConstant; + result = (refConstant.getClassName(clazz) + ' ' + + refConstant.getName(clazz) + ' ' + + refConstant.getType(clazz)) + .compareTo + (otherRefConstant.getClassName(clazz) + ' ' + + otherRefConstant.getName(clazz) + ' ' + + otherRefConstant.getType(clazz)); + } + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + result = classConstant.getName(clazz).compareTo(((ClassConstant)otherConstant).getName(clazz)); + } + + public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) + { + NameAndTypeConstant otherNameAndTypeConstant = (NameAndTypeConstant)otherConstant; + result = (nameAndTypeConstant.getName(clazz) + ' ' + + nameAndTypeConstant.getType(clazz)) + .compareTo + (otherNameAndTypeConstant.getName(clazz) + ' ' + + otherNameAndTypeConstant.getType(clazz)); + } + + + // Implementations for Object. + + public boolean equals(Object other) + { + return other != null && + this.getClass().equals(other.getClass()) && + this.getConstant().getClass().equals(((ComparableConstant)other).getConstant().getClass()) && + this.compareTo(other) == 0; + } + + + public int hashCode() + { + return this.getClass().hashCode(); + } +} diff --git a/src/proguard/classfile/editor/ConstantAdder.java b/src/proguard/classfile/editor/ConstantAdder.java new file mode 100644 index 0000000..2b74f5f --- /dev/null +++ b/src/proguard/classfile/editor/ConstantAdder.java @@ -0,0 +1,194 @@ +/* + * 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.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; + +/** + * This ConstantVisitor adds all constants that it visits to the constant pool + * of a given target class. + * + * @author Eric Lafortune + */ +public class ConstantAdder +implements ConstantVisitor +{ + private final ConstantPoolEditor constantPoolEditor; + + private int constantIndex; + + + /** + * Creates a new ConstantAdder that will copy constants into the given + * target class. + */ + public ConstantAdder(ProgramClass targetClass) + { + constantPoolEditor = new ConstantPoolEditor(targetClass); + } + + + /** + * Adds a copy of the specified constant in the given class and returns + * its index. If the specified index is 0, the returned value is 0 too. + */ + public int addConstant(Clazz clazz, int constantIndex) + { + clazz.constantPoolEntryAccept(constantIndex, this); + + return this.constantIndex; + } + + + /** + * Adds a copy of the given constant in the given class and returns + * its index. + */ + public int addConstant(Clazz clazz, Constant constant) + { + constant.accept(clazz, this); + + return this.constantIndex; + } + + + /** + * Returns the index of the most recently created constant in the constant + * pool of the target class. + */ + public int getConstantIndex() + { + return constantIndex; + } + + + // Implementations for ConstantVisitor. + + public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) + { + constantIndex = + constantPoolEditor.addIntegerConstant(integerConstant.getValue()); + } + + + public void visitLongConstant(Clazz clazz, LongConstant longConstant) + { + constantIndex = + constantPoolEditor.addLongConstant(longConstant.getValue()); + } + + + public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) + { + constantIndex = + constantPoolEditor.addFloatConstant(floatConstant.getValue()); + } + + + public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) + { + constantIndex = + constantPoolEditor.addDoubleConstant(doubleConstant.getValue()); + } + + + public void visitStringConstant(Clazz clazz, StringConstant stringConstant) + { + constantIndex = + constantPoolEditor.addStringConstant(stringConstant.getString(clazz), + stringConstant.referencedClass, + stringConstant.referencedMember); + } + + + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) + { + constantIndex = + constantPoolEditor.addUtf8Constant(utf8Constant.getString()); + } + + + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) + { + // First add the referenced class constant, with its own referenced class. + clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this); + + // Then add the actual field reference constant, with its referenced + // class and class member. + constantIndex = + constantPoolEditor.addFieldrefConstant(constantIndex, + fieldrefConstant.getName(clazz), + fieldrefConstant.getType(clazz), + fieldrefConstant.referencedClass, + fieldrefConstant.referencedMember); + } + + + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) + { + // First add the referenced class constant, with its own referenced class. + clazz.constantPoolEntryAccept(interfaceMethodrefConstant.u2classIndex, this); + + // Then add the actual interface method reference constant, with its + // referenced class and class member. + constantIndex = + constantPoolEditor.addInterfaceMethodrefConstant(constantIndex, + interfaceMethodrefConstant.getName(clazz), + interfaceMethodrefConstant.getType(clazz), + interfaceMethodrefConstant.referencedClass, + interfaceMethodrefConstant.referencedMember); + } + + + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) + { + // First add the referenced class constant, with its own referenced class. + clazz.constantPoolEntryAccept(methodrefConstant.u2classIndex, this); + + // Then add the actual method reference constant, with its referenced + // class and class member. + constantIndex = + constantPoolEditor.addMethodrefConstant(constantIndex, + methodrefConstant.getName(clazz), + methodrefConstant.getType(clazz), + methodrefConstant.referencedClass, + methodrefConstant.referencedMember); + } + + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + // Add the class constant, with its referenced class.. + constantIndex = + constantPoolEditor.addClassConstant(classConstant.getName(clazz), + classConstant.referencedClass); + } + + + public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) + { + constantIndex = + constantPoolEditor.addNameAndTypeConstant(nameAndTypeConstant.getName(clazz), + nameAndTypeConstant.getType(clazz)); + } +} diff --git a/src/proguard/classfile/editor/ConstantPoolEditor.java b/src/proguard/classfile/editor/ConstantPoolEditor.java new file mode 100644 index 0000000..8663dee --- /dev/null +++ b/src/proguard/classfile/editor/ConstantPoolEditor.java @@ -0,0 +1,665 @@ +/* + * 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.constant.*; + +/** + * This class can add constant pool entries to a given class. + * + * @author Eric Lafortune + */ +public class ConstantPoolEditor +{ + private static final boolean DEBUG = false; + + private ProgramClass targetClass; + + + /** + * Creates a new ConstantPoolEditor that will edit constants in the given + * target class. + */ + public ConstantPoolEditor(ProgramClass targetClass) + { + this.targetClass = targetClass; + } + + + /** + * Finds or creates a IntegerConstant constant pool entry with the given + * value. + * @return the constant pool index of the Utf8Constant. + */ + public int addIntegerConstant(int value) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Integer) + { + IntegerConstant integerConstant = (IntegerConstant)constant; + if (integerConstant.getValue() == value) + { + return index; + } + } + } + + return addConstant(new IntegerConstant(value)); + } + + + /** + * Finds or creates a LongConstant constant pool entry with the given value. + * @return the constant pool index of the LongConstant. + */ + public int addLongConstant(long value) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Long) + { + LongConstant longConstant = (LongConstant)constant; + if (longConstant.getValue() == value) + { + return index; + } + } + } + + return addConstant(new LongConstant(value)); + } + + + /** + * Finds or creates a FloatConstant constant pool entry with the given + * value. + * @return the constant pool index of the FloatConstant. + */ + public int addFloatConstant(float value) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Float) + { + FloatConstant floatConstant = (FloatConstant)constant; + if (floatConstant.getValue() == value) + { + return index; + } + } + } + + return addConstant(new FloatConstant(value)); + } + + + /** + * Finds or creates a DoubleConstant constant pool entry with the given + * value. + * @return the constant pool index of the DoubleConstant. + */ + public int addDoubleConstant(double value) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Double) + { + DoubleConstant doubleConstant = (DoubleConstant)constant; + if (doubleConstant.getValue() == value) + { + return index; + } + } + } + + return addConstant(new DoubleConstant(value)); + } + + + /** + * Finds or creates a StringConstant constant pool entry with the given + * value. + * @return the constant pool index of the StringConstant. + */ + public int addStringConstant(String string, + Clazz referencedClass, + Member referencedMember) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_String) + { + StringConstant stringConstant = (StringConstant)constant; + if (stringConstant.getString(targetClass).equals(string)) + { + return index; + } + } + } + + return addConstant(new StringConstant(addUtf8Constant(string), + referencedClass, + referencedMember)); + } + + + /** + * Finds or creates a FieldrefConstant constant pool entry for the given + * class and field. + * @return the constant pool index of the FieldrefConstant. + */ + public int addFieldrefConstant(Clazz referencedClass, + Member referencedMember) + { + return addFieldrefConstant(referencedClass.getName(), + referencedMember.getName(referencedClass), + referencedMember.getDescriptor(referencedClass), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a FieldrefConstant constant pool entry with the given + * class name, field name, and descriptor. + * @return the constant pool index of the FieldrefConstant. + */ + public int addFieldrefConstant(String className, + String name, + String descriptor, + Clazz referencedClass, + Member referencedMember) + { + return addFieldrefConstant(className, + addNameAndTypeConstant(name, descriptor), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a FieldrefConstant constant pool entry with the given + * class name, field name, and descriptor. + * @return the constant pool index of the FieldrefConstant. + */ + public int addFieldrefConstant(String className, + int nameAndTypeIndex, + Clazz referencedClass, + Member referencedMember) + { + return addFieldrefConstant(addClassConstant(className, referencedClass), + nameAndTypeIndex, + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a FieldrefConstant constant pool entry with the given + * class constant pool entry index, field name, and descriptor. + * @return the constant pool index of the FieldrefConstant. + */ + public int addFieldrefConstant(int classIndex, + String name, + String descriptor, + Clazz referencedClass, + Member referencedMember) + { + return addFieldrefConstant(classIndex, + addNameAndTypeConstant(name, descriptor), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a FieldrefConstant constant pool entry with the given + * class constant pool entry index and name and type constant pool entry + * index. + * @return the constant pool index of the FieldrefConstant. + */ + public int addFieldrefConstant(int classIndex, + int nameAndTypeIndex, + Clazz referencedClass, + Member referencedMember) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Fieldref) + { + FieldrefConstant fieldrefConstant = (FieldrefConstant)constant; + if (fieldrefConstant.u2classIndex == classIndex && + fieldrefConstant.u2nameAndTypeIndex == nameAndTypeIndex) + { + return index; + } + } + } + + return addConstant(new FieldrefConstant(classIndex, + nameAndTypeIndex, + referencedClass, + referencedMember)); + } + + + /** + * Finds or creates a InterfaceMethodrefConstant constant pool entry with the + * given class name, method name, and descriptor. + * @return the constant pool index of the InterfaceMethodrefConstant. + */ + public int addInterfaceMethodrefConstant(String className, + String name, + String descriptor, + Clazz referencedClass, + Member referencedMember) + { + return addInterfaceMethodrefConstant(className, + addNameAndTypeConstant(name, descriptor), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a InterfaceMethodrefConstant constant pool entry with the + * given class name, method name, and descriptor. + * @return the constant pool index of the InterfaceMethodrefConstant. + */ + public int addInterfaceMethodrefConstant(String className, + int nameAndTypeIndex, + Clazz referencedClass, + Member referencedMember) + { + return addInterfaceMethodrefConstant(addClassConstant(className, referencedClass), + nameAndTypeIndex, + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a InterfaceMethodrefConstant constant pool entry for the + * given class and method. + * @return the constant pool index of the InterfaceMethodrefConstant. + */ + public int addInterfaceMethodrefConstant(Clazz referencedClass, + Member referencedMember) + { + return addInterfaceMethodrefConstant(referencedClass.getName(), + referencedMember.getName(referencedClass), + referencedMember.getDescriptor(referencedClass), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a InterfaceMethodrefConstant constant pool entry with the + * given class constant pool entry index, method name, and descriptor. + * @return the constant pool index of the InterfaceMethodrefConstant. + */ + public int addInterfaceMethodrefConstant(int classIndex, + String name, + String descriptor, + Clazz referencedClass, + Member referencedMember) + { + return addInterfaceMethodrefConstant(classIndex, + addNameAndTypeConstant(name, descriptor), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a InterfaceMethodrefConstant constant pool entry with the + * given class constant pool entry index and name and type constant pool + * entry index. + * @return the constant pool index of the InterfaceMethodrefConstant. + */ + public int addInterfaceMethodrefConstant(int classIndex, + int nameAndTypeIndex, + Clazz referencedClass, + Member referencedMember) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_InterfaceMethodref) + { + InterfaceMethodrefConstant methodrefConstant = (InterfaceMethodrefConstant)constant; + if (methodrefConstant.u2classIndex == classIndex && + methodrefConstant.u2nameAndTypeIndex == nameAndTypeIndex) + { + return index; + } + } + } + + return addConstant(new InterfaceMethodrefConstant(classIndex, + nameAndTypeIndex, + referencedClass, + referencedMember)); + } + + + /** + * Finds or creates a MethodrefConstant constant pool entry for the given + * class and method. + * @return the constant pool index of the MethodrefConstant. + */ + public int addMethodrefConstant(Clazz referencedClass, + Member referencedMember) + { + return addMethodrefConstant(referencedClass.getName(), + referencedMember.getName(referencedClass), + referencedMember.getDescriptor(referencedClass), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a MethodrefConstant constant pool entry with the given + * class name, method name, and descriptor. + * @return the constant pool index of the MethodrefConstant. + */ + public int addMethodrefConstant(String className, + String name, + String descriptor, + Clazz referencedClass, + Member referencedMember) + { + return addMethodrefConstant(className, + addNameAndTypeConstant(name, descriptor), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a MethodrefConstant constant pool entry with the given + * class name, method name, and descriptor. + * @return the constant pool index of the MethodrefConstant. + */ + public int addMethodrefConstant(String className, + int nameAndTypeIndex, + Clazz referencedClass, + Member referencedMember) + { + return addMethodrefConstant(addClassConstant(className, referencedClass), + nameAndTypeIndex, + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a MethodrefConstant constant pool entry with the given + * class constant pool entry index, method name, and descriptor. + * @return the constant pool index of the MethodrefConstant. + */ + public int addMethodrefConstant(int classIndex, + String name, + String descriptor, + Clazz referencedClass, + Member referencedMember) + { + return addMethodrefConstant(classIndex, + addNameAndTypeConstant(name, descriptor), + referencedClass, + referencedMember); + } + + + /** + * Finds or creates a MethodrefConstant constant pool entry with the given + * class constant pool entry index and name and type constant pool entry + * index. + * @return the constant pool index of the MethodrefConstant. + */ + public int addMethodrefConstant(int classIndex, + int nameAndTypeIndex, + Clazz referencedClass, + Member referencedMember) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Methodref) + { + MethodrefConstant methodrefConstant = (MethodrefConstant)constant; + if (methodrefConstant.u2classIndex == classIndex && + methodrefConstant.u2nameAndTypeIndex == nameAndTypeIndex) + { + return index; + } + } + } + + return addConstant(new MethodrefConstant(classIndex, + nameAndTypeIndex, + referencedClass, + referencedMember)); + } + + + /** + * Finds or creates a ClassConstant constant pool entry for the given class. + * @return the constant pool index of the ClassConstant. + */ + public int addClassConstant(Clazz referencedClass) + { + return addClassConstant(referencedClass.getName(), + referencedClass); + } + + + /** + * Finds or creates a ClassConstant constant pool entry with the given name. + * @return the constant pool index of the ClassConstant. + */ + public int addClassConstant(String name, + Clazz referencedClass) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Class) + { + ClassConstant classConstant = (ClassConstant)constant; + if (classConstant.getName(targetClass).equals(name)) + { + return index; + } + } + } + + int nameIndex = addUtf8Constant(name); + + return addConstant(new ClassConstant(nameIndex, referencedClass)); + } + + + /** + * Finds or creates a NameAndTypeConstant constant pool entry with the given + * name and type. + * @return the constant pool index of the NameAndTypeConstant. + */ + public int addNameAndTypeConstant(String name, + String type) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_NameAndType) + { + NameAndTypeConstant nameAndTypeConstant = (NameAndTypeConstant)constant; + if (nameAndTypeConstant.getName(targetClass).equals(name) && + nameAndTypeConstant.getType(targetClass).equals(type)) + { + return index; + } + } + } + + return addConstant(new NameAndTypeConstant(addUtf8Constant(name), + addUtf8Constant(type))); + } + + + /** + * Finds or creates a Utf8Constant constant pool entry for the given string. + * @return the constant pool index of the Utf8Constant. + */ + public int addUtf8Constant(String string) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Check if the entry already exists. + for (int index = 1; index < constantPoolCount; index++) + { + Constant constant = constantPool[index]; + + if (constant != null && + constant.getTag() == ClassConstants.CONSTANT_Utf8) + { + Utf8Constant utf8Constant = (Utf8Constant)constant; + if (utf8Constant.getString().equals(string)) + { + return index; + } + } + } + + return addConstant(new Utf8Constant(string)); + } + + + /** + * Adds a given constant pool entry to the end of the constant pool/ + * @return the constant pool index for the added entry. + */ + public int addConstant(Constant constant) + { + int constantPoolCount = targetClass.u2constantPoolCount; + Constant[] constantPool = targetClass.constantPool; + + // Make sure there is enough space for another constant pool entry. + if (constantPool.length < constantPoolCount+2) + { + targetClass.constantPool = new Constant[constantPoolCount+2]; + System.arraycopy(constantPool, 0, + targetClass.constantPool, 0, + constantPoolCount); + constantPool = targetClass.constantPool; + } + + if (DEBUG) + { + System.out.println(targetClass.getName()+": adding ["+constant.getClass().getName()+"] at index "+targetClass.u2constantPoolCount); + } + + // Create a new Utf8Constant for the given string. + constantPool[targetClass.u2constantPoolCount++] = constant; + + // Long constants and double constants take up two entries in the + // constant pool. + int tag = constant.getTag(); + if (tag == ClassConstants.CONSTANT_Long || + tag == ClassConstants.CONSTANT_Double) + { + constantPool[targetClass.u2constantPoolCount++] = null; + } + + return constantPoolCount; + } +} diff --git a/src/proguard/classfile/editor/ConstantPoolRemapper.java b/src/proguard/classfile/editor/ConstantPoolRemapper.java new file mode 100644 index 0000000..7430d3d --- /dev/null +++ b/src/proguard/classfile/editor/ConstantPoolRemapper.java @@ -0,0 +1,617 @@ +/* + * 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.preverification.*; +import proguard.classfile.attribute.preverification.visitor.*; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.*; + +/** + * This ClassVisitor remaps all possible references to constant pool entries + * of the classes that it visits, based on a given index map. It is assumed that + * the constant pool entries themselves have already been remapped. + * + * @author Eric Lafortune + */ +public class ConstantPoolRemapper +extends SimplifiedVisitor +implements ClassVisitor, + ConstantVisitor, + MemberVisitor, + AttributeVisitor, + InstructionVisitor, + InnerClassesInfoVisitor, + ExceptionInfoVisitor, + StackMapFrameVisitor, + VerificationTypeVisitor, + LocalVariableInfoVisitor, + LocalVariableTypeInfoVisitor, + AnnotationVisitor, + ElementValueVisitor +{ + private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + + private int[] constantIndexMap; + + + /** + * Sets the given mapping of old constant pool entry indexes to their new + * indexes. + */ + public void setConstantIndexMap(int[] constantIndexMap) + { + this.constantIndexMap = constantIndexMap; + } + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + // Remap the local constant pool references. + programClass.u2thisClass = remapConstantIndex(programClass.u2thisClass); + programClass.u2superClass = remapConstantIndex(programClass.u2superClass); + + remapConstantIndexArray(programClass.u2interfaces, + programClass.u2interfacesCount); + + // Remap the references of the contant pool entries themselves. + programClass.constantPoolEntriesAccept(this); + + // Remap the references in all fields, methods, and attributes. + programClass.fieldsAccept(this); + programClass.methodsAccept(this); + programClass.attributesAccept(this); + } + + + public void visitLibraryClass(LibraryClass libraryClass) + { + } + + + // Implementations for ConstantVisitor. + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + classConstant.u2nameIndex = + remapConstantIndex(classConstant.u2nameIndex); + } + + + public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) + { + // Nothing to do. + } + + + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) + { + fieldrefConstant.u2classIndex = + remapConstantIndex(fieldrefConstant.u2classIndex); + fieldrefConstant.u2nameAndTypeIndex = + remapConstantIndex(fieldrefConstant.u2nameAndTypeIndex); + } + + + public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) + { + // Nothing to do. + } + + + public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) + { + // Nothing to do. + } + + + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) + { + interfaceMethodrefConstant.u2classIndex = + remapConstantIndex(interfaceMethodrefConstant.u2classIndex); + interfaceMethodrefConstant.u2nameAndTypeIndex = + remapConstantIndex(interfaceMethodrefConstant.u2nameAndTypeIndex); + } + + + public void visitLongConstant(Clazz clazz, LongConstant longConstant) + { + // Nothing to do. + } + + + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) + { + methodrefConstant.u2classIndex = + remapConstantIndex(methodrefConstant.u2classIndex); + methodrefConstant.u2nameAndTypeIndex = + remapConstantIndex(methodrefConstant.u2nameAndTypeIndex); + } + + + public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) + { + nameAndTypeConstant.u2nameIndex = + remapConstantIndex(nameAndTypeConstant.u2nameIndex); + nameAndTypeConstant.u2descriptorIndex = + remapConstantIndex(nameAndTypeConstant.u2descriptorIndex); + } + + + public void visitStringConstant(Clazz clazz, StringConstant stringConstant) + { + stringConstant.u2stringIndex = + remapConstantIndex(stringConstant.u2stringIndex); + } + + + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) + { + // Nothing to do. + } + + + // Implementations for MemberVisitor. + + public void visitProgramField(ProgramClass programClass, ProgramField programField) + { + visitMember(programClass, programField); + } + + + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { + visitMember(programClass, programMethod); + } + + + private void visitMember(ProgramClass programClass, ProgramMember programMember) + { + // Remap the local constant pool references. + programMember.u2nameIndex = + remapConstantIndex(programMember.u2nameIndex); + programMember.u2descriptorIndex = + remapConstantIndex(programMember.u2descriptorIndex); + + // Remap the constant pool references of the remaining attributes. + programMember.attributesAccept(programClass, this); + } + + + public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) + { + // Library classes are left unchanged. + } + + + public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) + { + // Library classes are left unchanged. + } + + + // Implementations for AttributeVisitor. + + public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute) + { + unknownAttribute.u2attributeNameIndex = + remapConstantIndex(unknownAttribute.u2attributeNameIndex); + + // There's not much else we can do with unknown attributes. + } + + + public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute) + { + sourceFileAttribute.u2attributeNameIndex = + remapConstantIndex(sourceFileAttribute.u2attributeNameIndex); + sourceFileAttribute.u2sourceFileIndex = + remapConstantIndex(sourceFileAttribute.u2sourceFileIndex); + } + + + public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute) + { + sourceDirAttribute.u2attributeNameIndex = + remapConstantIndex(sourceDirAttribute.u2attributeNameIndex); + sourceDirAttribute.u2sourceDirIndex = + remapConstantIndex(sourceDirAttribute.u2sourceDirIndex); + } + + + public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) + { + innerClassesAttribute.u2attributeNameIndex = + remapConstantIndex(innerClassesAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the inner classes. + innerClassesAttribute.innerClassEntriesAccept(clazz, this); + } + + + public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) + { + enclosingMethodAttribute.u2attributeNameIndex = + remapConstantIndex(enclosingMethodAttribute.u2attributeNameIndex); + enclosingMethodAttribute.u2classIndex = + remapConstantIndex(enclosingMethodAttribute.u2classIndex); + enclosingMethodAttribute.u2nameAndTypeIndex = + remapConstantIndex(enclosingMethodAttribute.u2nameAndTypeIndex); + } + + + public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute) + { + deprecatedAttribute.u2attributeNameIndex = + remapConstantIndex(deprecatedAttribute.u2attributeNameIndex); + } + + + public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute) + { + syntheticAttribute.u2attributeNameIndex = + remapConstantIndex(syntheticAttribute.u2attributeNameIndex); + } + + + public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) + { + signatureAttribute.u2attributeNameIndex = + remapConstantIndex(signatureAttribute.u2attributeNameIndex); + signatureAttribute.u2signatureIndex = + remapConstantIndex(signatureAttribute.u2signatureIndex); + } + + + public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute) + { + constantValueAttribute.u2attributeNameIndex = + remapConstantIndex(constantValueAttribute.u2attributeNameIndex); + constantValueAttribute.u2constantValueIndex = + remapConstantIndex(constantValueAttribute.u2constantValueIndex); + } + + + public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute) + { + exceptionsAttribute.u2attributeNameIndex = + remapConstantIndex(exceptionsAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the exceptions. + remapConstantIndexArray(exceptionsAttribute.u2exceptionIndexTable, + exceptionsAttribute.u2exceptionIndexTableLength); + } + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + codeAttribute.u2attributeNameIndex = + remapConstantIndex(codeAttribute.u2attributeNameIndex); + + // Initially, the code attribute editor doesn't contain any changes. + codeAttributeEditor.reset(codeAttribute.u4codeLength); + + // Remap the constant pool references of the instructions. + codeAttribute.instructionsAccept(clazz, method, this); + + // Apply the code atribute editor. It will only contain any changes if + // the code length is changing at any point. + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + + // Remap the constant pool references of the exceptions and attributes. + codeAttribute.exceptionsAccept(clazz, method, this); + codeAttribute.attributesAccept(clazz, method, this); + } + + + public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) + { + stackMapAttribute.u2attributeNameIndex = + remapConstantIndex(stackMapAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the stack map frames. + stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); + } + + + public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) + { + stackMapTableAttribute.u2attributeNameIndex = + remapConstantIndex(stackMapTableAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the stack map frames. + stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this); + } + + + public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) + { + lineNumberTableAttribute.u2attributeNameIndex = + remapConstantIndex(lineNumberTableAttribute.u2attributeNameIndex); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + localVariableTableAttribute.u2attributeNameIndex = + remapConstantIndex(localVariableTableAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the local variables. + localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + localVariableTypeTableAttribute.u2attributeNameIndex = + remapConstantIndex(localVariableTypeTableAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the local variables. + localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + } + + + public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) + { + annotationsAttribute.u2attributeNameIndex = + remapConstantIndex(annotationsAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the annotations. + annotationsAttribute.annotationsAccept(clazz, this); + } + + + public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) + { + parameterAnnotationsAttribute.u2attributeNameIndex = + remapConstantIndex(parameterAnnotationsAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the annotations. + parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); + } + + + public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) + { + annotationDefaultAttribute.u2attributeNameIndex = + remapConstantIndex(annotationDefaultAttribute.u2attributeNameIndex); + + // Remap the constant pool references of the annotations. + annotationDefaultAttribute.defaultValueAccept(clazz, this); + } + + + // Implementations for InnerClassesInfoVisitor. + + public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) + { + if (innerClassesInfo.u2innerClassIndex != 0) + { + innerClassesInfo.u2innerClassIndex = + remapConstantIndex(innerClassesInfo.u2innerClassIndex); + } + + if (innerClassesInfo.u2outerClassIndex != 0) + { + innerClassesInfo.u2outerClassIndex = + remapConstantIndex(innerClassesInfo.u2outerClassIndex); + } + + if (innerClassesInfo.u2innerNameIndex != 0) + { + innerClassesInfo.u2innerNameIndex = + remapConstantIndex(innerClassesInfo.u2innerNameIndex); + } + } + + + // Implementations for ExceptionInfoVisitor. + + public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) + { + if (exceptionInfo.u2catchType != 0) + { + exceptionInfo.u2catchType = + remapConstantIndex(exceptionInfo.u2catchType); + } + } + + + // Implementations for InstructionVisitor. + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + // Is the new constant pool index different from the original one? + int newConstantIndex = remapConstantIndex(constantInstruction.constantIndex); + if (newConstantIndex != constantInstruction.constantIndex) + { + // Replace the instruction. + Instruction replacementInstruction = + new ConstantInstruction(constantInstruction.opcode, + newConstantIndex, + constantInstruction.constant).shrink(); + + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); + } + } + + + // Implementations for StackMapFrameVisitor. + + public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) {} + + + public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame) + { + // Remap the constant pool references of the verification types. + sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this); + } + + + public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame) + { + // Remap the constant pool references of the verification types. + moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this); + } + + + public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame) + { + // Remap the constant pool references of the verification types. + fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this); + fullFrame.stackAccept(clazz, method, codeAttribute, offset, this); + } + + + // Implementations for VerificationTypeVisitor. + + public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {} + + + public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType) + { + objectType.u2classIndex = + remapConstantIndex(objectType.u2classIndex); + } + + + // Implementations for LocalVariableInfoVisitor. + + public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) + { + localVariableInfo.u2nameIndex = + remapConstantIndex(localVariableInfo.u2nameIndex); + localVariableInfo.u2descriptorIndex = + remapConstantIndex(localVariableInfo.u2descriptorIndex); + } + + + // Implementations for LocalVariableTypeInfoVisitor. + + public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) + { + localVariableTypeInfo.u2nameIndex = + remapConstantIndex(localVariableTypeInfo.u2nameIndex); + localVariableTypeInfo.u2signatureIndex = + remapConstantIndex(localVariableTypeInfo.u2signatureIndex); + } + + + // Implementations for AnnotationVisitor. + + public void visitAnnotation(Clazz clazz, Annotation annotation) + { + annotation.u2typeIndex = + remapConstantIndex(annotation.u2typeIndex); + + // Remap the constant pool references of the element values. + annotation.elementValuesAccept(clazz, this); + } + + + // Implementations for ElementValueVisitor. + + public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) + { + constantElementValue.u2elementNameIndex = + remapConstantIndex(constantElementValue.u2elementNameIndex); + constantElementValue.u2constantValueIndex = + remapConstantIndex(constantElementValue.u2constantValueIndex); + } + + + public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) + { + enumConstantElementValue.u2elementNameIndex = + remapConstantIndex(enumConstantElementValue.u2elementNameIndex); + enumConstantElementValue.u2typeNameIndex = + remapConstantIndex(enumConstantElementValue.u2typeNameIndex); + enumConstantElementValue.u2constantNameIndex = + remapConstantIndex(enumConstantElementValue.u2constantNameIndex); + } + + + public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) + { + classElementValue.u2elementNameIndex = + remapConstantIndex(classElementValue.u2elementNameIndex); + classElementValue.u2classInfoIndex = + remapConstantIndex(classElementValue.u2classInfoIndex); + } + + + public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) + { + annotationElementValue.u2elementNameIndex = + remapConstantIndex(annotationElementValue.u2elementNameIndex); + + // Remap the constant pool references of the annotation. + annotationElementValue.annotationAccept(clazz, this); + } + + + public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) + { + arrayElementValue.u2elementNameIndex = + remapConstantIndex(arrayElementValue.u2elementNameIndex); + + // Remap the constant pool references of the element values. + arrayElementValue.elementValuesAccept(clazz, annotation, this); + } + + + // Small utility methods. + + /** + * Remaps all constant pool indices in the given array. + */ + private void remapConstantIndexArray(int[] array, int length) + { + for (int index = 0; index < length; index++) + { + array[index] = remapConstantIndex(array[index]); + } + } + + + /** + * Returns the new constant pool index of the entry at the + * given index. + */ + private int remapConstantIndex(int constantIndex) + { + return constantIndexMap[constantIndex]; + } +} diff --git a/src/proguard/classfile/editor/ConstantPoolSorter.java b/src/proguard/classfile/editor/ConstantPoolSorter.java new file mode 100644 index 0000000..faae318 --- /dev/null +++ b/src/proguard/classfile/editor/ConstantPoolSorter.java @@ -0,0 +1,126 @@ +/* + * 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.constant.Constant; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.ClassVisitor; + +import java.util.Arrays; + +/** + * This ClassVisitor sorts the constant pool entries of the program classes + * that it visits. The sorting order is based on the types of the constant pool + * entries in the first place, and on their contents in the second place. + * + * @author Eric Lafortune + */ +public class ConstantPoolSorter +extends SimplifiedVisitor +implements ClassVisitor +{ + private int[] constantIndexMap = new int[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE]; + private ComparableConstant[] comparableConstantPool = new ComparableConstant[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE]; + private Constant[] newConstantPool = new Constant[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE]; + + private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper(); + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + int constantPoolCount = programClass.u2constantPoolCount; + + // Sort the constant pool and set up an index map. + if (constantIndexMap.length < constantPoolCount) + { + constantIndexMap = new int[constantPoolCount]; + comparableConstantPool = new ComparableConstant[constantPoolCount]; + newConstantPool = new Constant[constantPoolCount]; + } + + // Initialize an array whose elements can be compared. + int sortLength = 0; + for (int oldIndex = 1; oldIndex < constantPoolCount; oldIndex++) + { + Constant constant = programClass.constantPool[oldIndex]; + if (constant != null) + { + comparableConstantPool[sortLength++] = + new ComparableConstant(programClass, oldIndex, constant); + } + } + + // Sort the array. + Arrays.sort(comparableConstantPool, 0, sortLength); + + // Save the sorted elements. + int newLength = 1; + int newIndex = 1; + ComparableConstant previousComparableConstant = null; + for (int sortIndex = 0; sortIndex < sortLength; sortIndex++) + { + ComparableConstant comparableConstant = comparableConstantPool[sortIndex]; + + // Isn't this a duplicate of the previous constant? + if (!comparableConstant.equals(previousComparableConstant)) + { + // Remember the index of the new entry. + newIndex = newLength; + + // Copy the sorted constant pool entry over to the constant pool. + Constant constant = comparableConstant.getConstant(); + + newConstantPool[newLength++] = constant; + + // Long entries take up two slots, the second of which is null. + int tag = constant.getTag(); + if (tag == ClassConstants.CONSTANT_Long || + tag == ClassConstants.CONSTANT_Double) + { + newConstantPool[newLength++] = null; + } + + previousComparableConstant = comparableConstant; + } + + // Fill out the map array. + constantIndexMap[comparableConstant.getIndex()] = newIndex; + } + + // Copy the new constant pool over. + System.arraycopy(newConstantPool, 0, programClass.constantPool, 0, newLength); + + // Clear any remaining entries. + for (int index = newLength; index < constantPoolCount; index++) + { + programClass.constantPool[index] = null; + } + + programClass.u2constantPoolCount = newLength; + + // Remap all constant pool references. + constantPoolRemapper.setConstantIndexMap(constantIndexMap); + constantPoolRemapper.visitProgramClass(programClass); + } +} diff --git a/src/proguard/classfile/editor/ElementValueAdder.java b/src/proguard/classfile/editor/ElementValueAdder.java new file mode 100644 index 0000000..8cbd11d --- /dev/null +++ b/src/proguard/classfile/editor/ElementValueAdder.java @@ -0,0 +1,217 @@ +/* + * 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.annotation.*; +import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor; + +/** + * This AnnotationVisitor adds all element values that it visits to the given + * target annotation default attribute, annotation, or element value. + * + * @author Eric Lafortune + */ +public class ElementValueAdder +implements ElementValueVisitor +{ + private static final ElementValue[] EMPTY_ELEMENT_VALUES = new ElementValue[0]; + + + private final ProgramClass targetClass; + private final AnnotationDefaultAttribute targetAnnotationDefaultAttribute; + + private final ConstantAdder constantAdder; + private final ElementValuesEditor elementValuesEditor; + + + /** + * Creates a new ElementValueAdder that will copy element values into the + * given target annotation default attribute value. + */ + public ElementValueAdder(ProgramClass targetClass, + AnnotationDefaultAttribute targetAnnotationDefaultAttribute, + boolean replaceElementValues) + { + this.targetClass = targetClass; + this.targetAnnotationDefaultAttribute = targetAnnotationDefaultAttribute; + + constantAdder = new ConstantAdder(targetClass); + elementValuesEditor = null; + } + + + /** + * Creates a new ElementValueAdder that will copy element values into the + * given target annotation. + */ + public ElementValueAdder(ProgramClass targetClass, + Annotation targetAnnotation, + boolean replaceElementValues) + { + this.targetClass = targetClass; + this.targetAnnotationDefaultAttribute = null; + + constantAdder = new ConstantAdder(targetClass); + elementValuesEditor = new ElementValuesEditor(targetClass, + targetAnnotation, + replaceElementValues); + } + + + /** + * Creates a new ElementValueAdder that will copy element values into the + * given target element value. + */ + public ElementValueAdder(ProgramClass targetClass, + ArrayElementValue targetArrayElementValue, + boolean replaceElementValues) + { + this.targetClass = targetClass; + this.targetAnnotationDefaultAttribute = null; + + constantAdder = new ConstantAdder(targetClass); + elementValuesEditor = new ElementValuesEditor(targetClass, + targetArrayElementValue, + replaceElementValues); + } + + + // Implementations for ElementValueVisitor. + + public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) + { + // Create a copy of the element value. + ConstantElementValue newConstantElementValue = + new ConstantElementValue(constantElementValue.u1tag, + constantElementValue.u2elementNameIndex == 0 ? 0 : + constantAdder.addConstant(clazz, constantElementValue.u2elementNameIndex), + constantAdder.addConstant(clazz, constantElementValue.u2constantValueIndex)); + + newConstantElementValue.referencedClass = constantElementValue.referencedClass; + newConstantElementValue.referencedMethod = constantElementValue.referencedMethod; + + // Add it to the target. + addElementValue(newConstantElementValue); + } + + + public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) + { + // Create a copy of the element value. + EnumConstantElementValue newEnumConstantElementValue = + new EnumConstantElementValue(enumConstantElementValue.u2elementNameIndex == 0 ? 0 : + constantAdder.addConstant(clazz, enumConstantElementValue.u2elementNameIndex), + constantAdder.addConstant(clazz, enumConstantElementValue.u2typeNameIndex), + constantAdder.addConstant(clazz, enumConstantElementValue.u2constantNameIndex)); + + newEnumConstantElementValue.referencedClass = enumConstantElementValue.referencedClass; + newEnumConstantElementValue.referencedMethod = enumConstantElementValue.referencedMethod; + + // TODO: Clone array. + newEnumConstantElementValue.referencedClasses = enumConstantElementValue.referencedClasses; + + // Add it to the target. + addElementValue(newEnumConstantElementValue); + } + + + public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) + { + // Create a copy of the element value. + ClassElementValue newClassElementValue = + new ClassElementValue(classElementValue.u2elementNameIndex == 0 ? 0 : + constantAdder.addConstant(clazz, classElementValue.u2elementNameIndex), + constantAdder.addConstant(clazz, classElementValue.u2classInfoIndex)); + + newClassElementValue.referencedClass = classElementValue.referencedClass; + newClassElementValue.referencedMethod = classElementValue.referencedMethod; + + // TODO: Clone array. + newClassElementValue.referencedClasses = classElementValue.referencedClasses; + + // Add it to the target. + addElementValue(newClassElementValue); + } + + + public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) + { + // Create a copy of the element value. + AnnotationElementValue newAnnotationElementValue = + new AnnotationElementValue(annotationElementValue.u2elementNameIndex == 0 ? 0 : + constantAdder.addConstant(clazz, annotationElementValue.u2elementNameIndex), + new Annotation()); + + newAnnotationElementValue.referencedClass = annotationElementValue.referencedClass; + newAnnotationElementValue.referencedMethod = annotationElementValue.referencedMethod; + + annotationElementValue.annotationAccept(clazz, + new AnnotationAdder(targetClass, + newAnnotationElementValue)); + + // Add it to the target. + addElementValue(newAnnotationElementValue); + } + + + public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) + { + // Create a copy of the element value. + ArrayElementValue newArrayElementValue = + new ArrayElementValue(arrayElementValue.u2elementNameIndex == 0 ? 0 : + constantAdder.addConstant(clazz, arrayElementValue.u2elementNameIndex), + 0, + arrayElementValue.u2elementValuesCount > 0 ? + new ElementValue[arrayElementValue.u2elementValuesCount] : + EMPTY_ELEMENT_VALUES); + + newArrayElementValue.referencedClass = arrayElementValue.referencedClass; + newArrayElementValue.referencedMethod = arrayElementValue.referencedMethod; + + arrayElementValue.elementValuesAccept(clazz, + annotation, + new ElementValueAdder(targetClass, + newArrayElementValue, + false)); + + // Add it to the target. + addElementValue(newArrayElementValue); + } + + + // Small utility methods. + + private void addElementValue(ElementValue newElementValue) + { + // What's the target? + if (targetAnnotationDefaultAttribute != null) + { + // Simply set the completed element value. + targetAnnotationDefaultAttribute.defaultValue = newElementValue; + } + else + { + // Add it to the target. + elementValuesEditor.addElementValue(newElementValue); + } + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/ElementValuesEditor.java b/src/proguard/classfile/editor/ElementValuesEditor.java new file mode 100644 index 0000000..bfc4e9f --- /dev/null +++ b/src/proguard/classfile/editor/ElementValuesEditor.java @@ -0,0 +1,238 @@ +/* + * 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.annotation.*; + +/** + * This class can add and delete element values to and from a given target + * annotation default attribute, annotation, or array element value. Element + * values to be added must be filled out beforehand, including their references + * to the constant pool. + * + * @author Eric Lafortune + */ +public class ElementValuesEditor +{ + private final ProgramClass targetClass; + private final Annotation targetAnnotation; + private final ArrayElementValue targetArrayElementValue; + private final boolean replaceElementValues; + + + /** + * Creates a new ElementValuesEditor that will edit element values in the + * given target annotation. + */ + public ElementValuesEditor(ProgramClass targetClass, + Annotation targetAnnotation, + boolean replaceElementValues) + { + this.targetClass = targetClass; + this.targetAnnotation = targetAnnotation; + this.targetArrayElementValue = null; + this.replaceElementValues = replaceElementValues; + } + + + /** + * Creates a new ElementValuesEditor that will edit element values in the + * given target array element value. + */ + public ElementValuesEditor(ProgramClass targetClass, + ArrayElementValue targetArrayElementValue, + boolean replaceElementValues) + { + this.targetClass = targetClass; + this.targetAnnotation = null; + this.targetArrayElementValue = targetArrayElementValue; + this.replaceElementValues = replaceElementValues; + } + + + /** + * Adds the given elementValue to the target. + */ + public void addElementValue(ElementValue elementValue) + { + // What's the target? + if (targetAnnotation != null) + { + // Try to replace an existing element value. + if (!replaceElementValues || + !replaceElementValue(targetAnnotation.u2elementValuesCount, + targetAnnotation.elementValues, + elementValue)) + { + // Otherwise append the element value. + targetAnnotation.elementValues = + addElementValue(targetAnnotation.u2elementValuesCount, + targetAnnotation.elementValues, + elementValue); + + targetAnnotation.u2elementValuesCount++; + } + } + else + { + // Try to replace an existing element value. + if (!replaceElementValues || + !replaceElementValue(targetArrayElementValue.u2elementValuesCount, + targetArrayElementValue.elementValues, + elementValue)) + { + // Otherwise append the element value. + targetArrayElementValue.elementValues = + addElementValue(targetArrayElementValue.u2elementValuesCount, + targetArrayElementValue.elementValues, + elementValue); + + targetArrayElementValue.u2elementValuesCount++; + } + } + } + + + /** + * Deletes the given elementValue to the target. + */ + public void deleteElementValue(String elementValueMethodName) + { + // What's the target? + if (targetAnnotation != null) + { + // Delete the element value to the target annotation. + targetAnnotation.u2elementValuesCount = + deleteElementValue(targetAnnotation.u2elementValuesCount, + targetAnnotation.elementValues, + elementValueMethodName); + } + else + { + // Delete the element value to the target array element value. + targetArrayElementValue.u2elementValuesCount = + deleteElementValue(targetArrayElementValue.u2elementValuesCount, + targetArrayElementValue.elementValues, + elementValueMethodName); + } + } + + + // Small utility methods. + + /** + * Tries put the given element value in place of an existing element value + * of the same name, returning whether it was present. + */ + private boolean replaceElementValue(int elementValuesCount, + ElementValue[] elementValues, + ElementValue elementValue) + { + // Find the element value with the same name. + int index = findElementValue(elementValuesCount, + elementValues, + elementValue.getMethodName(targetClass)); + if (index < 0) + { + return false; + } + + elementValues[index] = elementValue; + + return true; + } + + + /** + * Appends the given element value to the given array of element values, + * creating a new array if necessary. + */ + private ElementValue[] addElementValue(int elementValuesCount, + ElementValue[] elementValues, + ElementValue elementValue) + { + // Is the array too small to contain the additional elementValue? + if (elementValues.length <= elementValuesCount) + { + // Create a new array and copy the elementValues into it. + ElementValue[] newElementValues = new ElementValue[elementValuesCount + 1]; + System.arraycopy(elementValues, 0, + newElementValues, 0, + elementValuesCount); + elementValues = newElementValues; + } + + // Append the elementValue. + elementValues[elementValuesCount] = elementValue; + + return elementValues; + } + + + /** + * Deletes the element values with the given name from the given array of + * element values, returning the new number of element values. + */ + private int deleteElementValue(int elementValuesCount, + ElementValue[] elementValues, + String elementValueMethodName) + { + // Find the element value. + int index = findElementValue(elementValuesCount, + elementValues, + elementValueMethodName); + if (index < 0) + { + return elementValuesCount; + } + + // Shift the other element values in the array. + System.arraycopy(elementValues, index + 1, + elementValues, index, + elementValuesCount - index - 1); + + // Clear the last entry in the array. + elementValues[--elementValuesCount] = null; + + return elementValuesCount; + } + + + /** + * Finds the index of the element value with the given name in the given + * array of element values. + */ + private int findElementValue(int elementValuesCount, + ElementValue[] elementValues, + String elementValueName) + { + for (int index = 0; index < elementValuesCount; index++) + { + if (elementValues[index].getMethodName(targetClass).equals(elementValueName)) + { + return index; + } + } + + return -1; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/ExceptionAdder.java b/src/proguard/classfile/editor/ExceptionAdder.java new file mode 100644 index 0000000..1ccb1a6 --- /dev/null +++ b/src/proguard/classfile/editor/ExceptionAdder.java @@ -0,0 +1,65 @@ +/* + * 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.ExceptionsAttribute; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This ConstantVisitor adds all class constants that it visits to the given + * target exceptions attribute. + * + * @author Eric Lafortune + */ +public class ExceptionAdder +extends SimplifiedVisitor +implements ConstantVisitor +{ + private final ConstantAdder constantAdder; + private final ExceptionsAttributeEditor exceptionsAttributeEditor; + + + /** + * Creates a new ExceptionAdder that will copy classes into the given + * target exceptions attribute. + */ + public ExceptionAdder(ProgramClass targetClass, + ExceptionsAttribute targetExceptionsAttribute) + { + constantAdder = new ConstantAdder(targetClass); + exceptionsAttributeEditor = new ExceptionsAttributeEditor(targetExceptionsAttribute); + } + + + // Implementations for ConstantVisitor. + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + // Add a class constant to the constant pool. + constantAdder.visitClassConstant(clazz, classConstant); + + // Add the index of the class constant to the list of exceptions. + exceptionsAttributeEditor.addException(constantAdder.getConstantIndex()); + } +} diff --git a/src/proguard/classfile/editor/ExceptionInfoAdder.java b/src/proguard/classfile/editor/ExceptionInfoAdder.java new file mode 100644 index 0000000..e0cc9c5 --- /dev/null +++ b/src/proguard/classfile/editor/ExceptionInfoAdder.java @@ -0,0 +1,67 @@ +/* + * 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.visitor.ExceptionInfoVisitor; + +/** + * This ExceptionInfoVisitor adds all exception information that it visits to + * the given target code attribute. + * + * @author Eric Lafortune + */ +public class ExceptionInfoAdder +implements ExceptionInfoVisitor +{ + private final ConstantAdder constantAdder; + private final CodeAttributeComposer codeAttributeComposer; + + + /** + * Creates a new ExceptionAdder that will copy exceptions into the given + * target code attribute. + */ + public ExceptionInfoAdder(ProgramClass targetClass, + CodeAttributeComposer targetComposer) + { + constantAdder = new ConstantAdder(targetClass); + codeAttributeComposer = targetComposer; + } + + + // Implementations for ExceptionInfoVisitor. + + public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) + { + // Create a copy of the exception info. + ExceptionInfo newExceptionInfo = + new ExceptionInfo(exceptionInfo.u2startPC, + exceptionInfo.u2endPC, + exceptionInfo.u2handlerPC, + exceptionInfo.u2catchType == 0 ? 0 : + constantAdder.addConstant(clazz, exceptionInfo.u2catchType)); + + // Add the completed exception info. + codeAttributeComposer.appendException(newExceptionInfo); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/ExceptionsAttributeEditor.java b/src/proguard/classfile/editor/ExceptionsAttributeEditor.java new file mode 100644 index 0000000..4509a9a --- /dev/null +++ b/src/proguard/classfile/editor/ExceptionsAttributeEditor.java @@ -0,0 +1,68 @@ +/* + * 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.attribute.ExceptionsAttribute; + +/** + * This class can add exceptions to a given exceptions attribute. + * Exceptions to be added must have been added to the constant pool and filled + * out beforehand. + * + * @author Eric Lafortune + */ +public class ExceptionsAttributeEditor +{ + private ExceptionsAttribute targetExceptionsAttribute; + + + /** + * Creates a new ExceptionsAttributeEditor that will edit exceptions in the + * given exceptions attribute. + */ + public ExceptionsAttributeEditor(ExceptionsAttribute targetExceptionsAttribute) + { + this.targetExceptionsAttribute = targetExceptionsAttribute; + } + + + /** + * Adds a given exception to the exceptions attribute. + */ + public void addException(int exceptionIndex) + { + int exceptionIndexTableLength = targetExceptionsAttribute.u2exceptionIndexTableLength; + int[] exceptionIndexTable = targetExceptionsAttribute.u2exceptionIndexTable; + + // Make sure there is enough space for the new exception. + if (exceptionIndexTable.length <= exceptionIndexTableLength) + { + targetExceptionsAttribute.u2exceptionIndexTable = new int[exceptionIndexTableLength+1]; + System.arraycopy(exceptionIndexTable, 0, + targetExceptionsAttribute.u2exceptionIndexTable, 0, + exceptionIndexTableLength); + exceptionIndexTable = targetExceptionsAttribute.u2exceptionIndexTable; + } + + // Add the exception. + exceptionIndexTable[targetExceptionsAttribute.u2exceptionIndexTableLength++] = exceptionIndex; + } +} diff --git a/src/proguard/classfile/editor/InstructionAdder.java b/src/proguard/classfile/editor/InstructionAdder.java new file mode 100644 index 0000000..60fde6d --- /dev/null +++ b/src/proguard/classfile/editor/InstructionAdder.java @@ -0,0 +1,76 @@ +/* + * 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.instruction.visitor.InstructionVisitor; +import proguard.classfile.instruction.*; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This InstructionVisitor adds all instructions that it visits to the given + * target code attribute. + * + * @author Eric Lafortune + */ +public class InstructionAdder +extends SimplifiedVisitor +implements InstructionVisitor +{ + private final ConstantAdder constantAdder; + private final CodeAttributeComposer codeAttributeComposer; + + + /** + * Creates a new InstructionAdder that will copy classes into the given + * target code attribute. + */ + public InstructionAdder(ProgramClass targetClass, + CodeAttributeComposer targetComposer) + { + constantAdder = new ConstantAdder(targetClass); + codeAttributeComposer = targetComposer; + } + + + // Implementations for InstructionVisitor. + + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) + { + // Add the instruction. + codeAttributeComposer.appendInstruction(offset, instruction); + } + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + // Create a copy of the instruction. + Instruction newConstantInstruction = + new ConstantInstruction(constantInstruction.opcode, + constantAdder.addConstant(clazz, constantInstruction.constantIndex), + constantInstruction.constant).shrink(); + + // Add the instruction. + codeAttributeComposer.appendInstruction(offset, newConstantInstruction); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/InstructionWriter.java b/src/proguard/classfile/editor/InstructionWriter.java new file mode 100644 index 0000000..d842358 --- /dev/null +++ b/src/proguard/classfile/editor/InstructionWriter.java @@ -0,0 +1,278 @@ +/* + * 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.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This InstructionVisitor writes out the instructions that it visits, + * collecting instructions that have to be widened. As an AttributeVisitor, + * it then applies the collected changes. The process will be repeated + * recursively, if necessary. + * + * @author Eric Lafortune + */ +public class InstructionWriter +extends SimplifiedVisitor +implements InstructionVisitor, + AttributeVisitor +{ + private int codeLength; + + private CodeAttributeEditor codeAttributeEditor; + + + /** + * Resets the accumulated code changes. + * @param codeLength the length of the code that will be edited next. + */ + public void reset(int codeLength) + { + this.codeLength = codeLength; + + // The code attribute editor has to be created lazily. + if (codeAttributeEditor != null) + { + codeAttributeEditor.reset(codeLength); + } + } + + + // Implementations for InstructionVisitor. + + public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) + { + // Try to write out the instruction. + // Simple instructions should always fit. + simpleInstruction.write(codeAttribute, offset); + } + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + try + { + // Try to write out the instruction. + constantInstruction.write(codeAttribute, offset); + } + catch (IllegalArgumentException exception) + { + // Create a new constant instruction that will fit. + Instruction replacementInstruction = + new ConstantInstruction(constantInstruction.opcode, + constantInstruction.constantIndex, + constantInstruction.constant).shrink(); + + replaceInstruction(offset, replacementInstruction); + + // Write out a dummy constant instruction for now. + constantInstruction.constantIndex = 0; + constantInstruction.constant = 0; + constantInstruction.write(codeAttribute, offset); + } + } + + + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) + { + try + { + // Try to write out the instruction. + variableInstruction.write(codeAttribute, offset); + } + catch (IllegalArgumentException exception) + { + // Create a new variable instruction that will fit. + Instruction replacementInstruction = + new VariableInstruction(variableInstruction.opcode, + variableInstruction.variableIndex, + variableInstruction.constant).shrink(); + + replaceInstruction(offset, replacementInstruction); + + // Write out a dummy variable instruction for now. + variableInstruction.variableIndex = 0; + variableInstruction.constant = 0; + variableInstruction.write(codeAttribute, offset); + } + } + + + public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) + { + try + { + // Try to write out the instruction. + branchInstruction.write(codeAttribute, offset); + } + catch (IllegalArgumentException exception) + { + // Create a new unconditional branch that will fit. + Instruction replacementInstruction = + new BranchInstruction(InstructionConstants.OP_GOTO_W, + branchInstruction.branchOffset); + + // Create a new instruction that will fit. + switch (branchInstruction.opcode) + { + default: + { + // Create a new branch instruction that will fit. + replacementInstruction = + new BranchInstruction(branchInstruction.opcode, + branchInstruction.branchOffset).shrink(); + + break; + } + + // Some special cases, for which a wide branch doesn't exist. + case InstructionConstants.OP_IFEQ: + case InstructionConstants.OP_IFNE: + case InstructionConstants.OP_IFLT: + case InstructionConstants.OP_IFGE: + case InstructionConstants.OP_IFGT: + case InstructionConstants.OP_IFLE: + case InstructionConstants.OP_IFICMPEQ: + case InstructionConstants.OP_IFICMPNE: + case InstructionConstants.OP_IFICMPLT: + case InstructionConstants.OP_IFICMPGE: + case InstructionConstants.OP_IFICMPGT: + case InstructionConstants.OP_IFICMPLE: + case InstructionConstants.OP_IFACMPEQ: + case InstructionConstants.OP_IFACMPNE: + { + // Insert the complementary conditional branch. + Instruction complementaryConditionalBranch = + new BranchInstruction((byte)(((branchInstruction.opcode+1) ^ 1) - 1), + (1+2) + (1+4)); + + insertBeforeInstruction(offset, complementaryConditionalBranch); + + // Create a new unconditional branch that will fit. + break; + } + + case InstructionConstants.OP_IFNULL: + case InstructionConstants.OP_IFNONNULL: + { + // Insert the complementary conditional branch. + Instruction complementaryConditionalBranch = + new BranchInstruction((byte)(branchInstruction.opcode ^ 1), + (1+2) + (1+4)); + + insertBeforeInstruction(offset, complementaryConditionalBranch); + + // Create a new unconditional branch that will fit. + break; + } + } + + replaceInstruction(offset, replacementInstruction); + + // Write out a dummy branch instruction for now. + branchInstruction.branchOffset = 0; + branchInstruction.write(codeAttribute, offset); + } + } + + + public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) + { + // Try to write out the instruction. + // Switch instructions should always fit. + switchInstruction.write(codeAttribute, offset); + } + + + // Implementations for AttributeVisitor. + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Avoid doing any work if nothing is changing anyway. + if (codeAttributeEditor != null) + { + // Apply the collected expansions. + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + + // Clear the modifications for the next run. + codeAttributeEditor = null; + } + } + + + // Small utility methods. + + /** + * Remembers to place the given instruction right before the instruction + * at the given offset. + */ + private void insertBeforeInstruction(int instructionOffset, Instruction instruction) + { + ensureCodeAttributeEditor(); + + // Replace the instruction. + codeAttributeEditor.insertBeforeInstruction(instructionOffset, instruction); + } + + + /** + * Remembers to replace the instruction at the given offset by the given + * instruction. + */ + private void replaceInstruction(int instructionOffset, Instruction instruction) + { + ensureCodeAttributeEditor(); + + // Replace the instruction. + codeAttributeEditor.replaceInstruction(instructionOffset, instruction); + } + + + /** + * Remembers to place the given instruction right after the instruction + * at the given offset. + */ + private void insertAfterInstruction(int instructionOffset, Instruction instruction) + { + ensureCodeAttributeEditor(); + + // Replace the instruction. + codeAttributeEditor.insertAfterInstruction(instructionOffset, instruction); + } + + + /** + * Makes sure there is a code attribute editor for the given code attribute. + */ + private void ensureCodeAttributeEditor() + { + if (codeAttributeEditor == null) + { + codeAttributeEditor = new CodeAttributeEditor(); + codeAttributeEditor.reset(codeLength); + } + } +} diff --git a/src/proguard/classfile/editor/InterfaceAdder.java b/src/proguard/classfile/editor/InterfaceAdder.java new file mode 100644 index 0000000..e095af6 --- /dev/null +++ b/src/proguard/classfile/editor/InterfaceAdder.java @@ -0,0 +1,62 @@ +/* + * 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.preverification.*; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This ConstantVisitor adds all interfaces that it visits to the given + * target class. + * + * @author Eric Lafortune + */ +public class InterfaceAdder +extends SimplifiedVisitor +implements ConstantVisitor +{ + private final ConstantAdder constantAdder; + private final InterfacesEditor interfacesEditor; + + + /** + * Creates a new InterfaceAdder that will add interfaces to the given + * target class. + */ + public InterfaceAdder(ProgramClass targetClass) + { + constantAdder = new ConstantAdder(targetClass); + interfacesEditor = new InterfacesEditor(targetClass); + } + + + // Implementations for ConstantVisitor. + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + interfacesEditor.addInterface(constantAdder.addConstant(clazz, classConstant)); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/InterfaceSorter.java b/src/proguard/classfile/editor/InterfaceSorter.java new file mode 100644 index 0000000..6521369 --- /dev/null +++ b/src/proguard/classfile/editor/InterfaceSorter.java @@ -0,0 +1,67 @@ +/* + * 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.util.SimplifiedVisitor; +import proguard.classfile.visitor.ClassVisitor; + +import java.util.Arrays; + +/** + * This ClassVisitor sorts the interfaces of the program classes that it visits. + * + * @author Eric Lafortune + */ +public class InterfaceSorter +extends SimplifiedVisitor +implements ClassVisitor +{ + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + int[] interfaces = programClass.u2interfaces; + int interfacesCount = programClass.u2interfacesCount; + + // Sort the interfaces. + Arrays.sort(interfaces, 0, interfacesCount); + + // Remove any duplicate entries. + int newInterfacesCount = 0; + int previousInterfaceIndex = 0; + for (int index = 0; index < interfacesCount; index++) + { + int interfaceIndex = interfaces[index]; + + // Isn't this a duplicate of the previous interface? + if (interfaceIndex != previousInterfaceIndex) + { + interfaces[newInterfacesCount++] = interfaceIndex; + + // Remember the interface. + previousInterfaceIndex = interfaceIndex; + } + } + + programClass.u2interfacesCount = newInterfacesCount; + } +} diff --git a/src/proguard/classfile/editor/InterfacesEditor.java b/src/proguard/classfile/editor/InterfacesEditor.java new file mode 100644 index 0000000..d3170e1 --- /dev/null +++ b/src/proguard/classfile/editor/InterfacesEditor.java @@ -0,0 +1,122 @@ +/* + * 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 java.util.Arrays; + +/** + * This class can add and delete interfaces to and from classes. References to + * the constant pool must be filled out beforehand. + * + * @author Eric Lafortune + */ +public class InterfacesEditor +{ + private final ProgramClass targetClass; + + + /** + * Creates a new InterfacesEditor that will edit interfaces in the given + * target class. + */ + public InterfacesEditor(ProgramClass targetClass) + { + this.targetClass = targetClass; + } + + + /** + * Adds the specified interface to the target class, if it isn't present yet. + */ + public void addInterface(int interfaceConstantIndex) + { + // Is the interface not yet present? + if (findInterfaceIndex(interfaceConstantIndex) < 0) + { + int interfacesCount = targetClass.u2interfacesCount++; + int[] interfaces = targetClass.u2interfaces; + + // Is the array too small to contain the additional interface? + if (interfaces.length <= interfacesCount) + { + // Create a new array and copy the interfaces into it. + int[] newinterfaces = new int[interfacesCount + 1]; + System.arraycopy(interfaces, 0, newinterfaces, 0, interfacesCount); + interfaces = newinterfaces; + + targetClass.u2interfaces = interfaces; + } + + // Append the interface. + interfaces[interfacesCount] = interfaceConstantIndex; + } + } + + + /** + * Deletes the given interface from the target class, if it is present. + */ + public void deleteInterface(int interfaceConstantIndex) + { + // Is the interface already present? + int interfaceIndex = findInterfaceIndex(interfaceConstantIndex); + if (interfaceIndex >= 0) + { + int interfacesCount = --targetClass.u2interfacesCount; + int[] interfaces = targetClass.u2interfaces; + + // Shift the other interfaces in the array. + for (int index = interfaceIndex; index < interfacesCount; index++) + { + interfaces[index] = interfaces[index + 1]; + } + + // Clear the remaining entry in the array. + interfaces[interfacesCount] = 0; + } + } + + + // Small utility methods. + + /** + * Finds the index of the specified interface in the list of interfaces of + * the target class. + */ + private int findInterfaceIndex(int interfaceConstantIndex) + { + int interfacesCount = targetClass.u2interfacesCount; + int[] interfaces = targetClass.u2interfaces; + + for (int index = 0; index < interfacesCount; index++) + { + if (interfaces[index] == interfaceConstantIndex) + { + return index; + } + } + + return -1; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/LineNumberInfoAdder.java b/src/proguard/classfile/editor/LineNumberInfoAdder.java new file mode 100644 index 0000000..aa8c0c4 --- /dev/null +++ b/src/proguard/classfile/editor/LineNumberInfoAdder.java @@ -0,0 +1,59 @@ +/* + * 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.attribute.visitor.LineNumberInfoVisitor; +import proguard.classfile.attribute.*; +import proguard.classfile.*; + +/** + * This LineNumberInfoVisitor adds all line numbers that it visits to the given + * target line number attribute. + */ +public class LineNumberInfoAdder +implements LineNumberInfoVisitor +{ + private final LineNumberTableAttributeEditor lineNumberTableAttributeEditor; + + + /** + * Creates a new LineNumberInfoAdder that will copy line numbers into the + * given target line number table. + */ + public LineNumberInfoAdder(LineNumberTableAttribute targetLineNumberTableAttribute) + { + this.lineNumberTableAttributeEditor = new LineNumberTableAttributeEditor(targetLineNumberTableAttribute); + } + + + // Implementations for LineNumberInfoVisitor. + + public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) + { + // Create a new line number. + LineNumberInfo newLineNumberInfo = + new LineNumberInfo(lineNumberInfo.u2startPC, + lineNumberInfo.u2lineNumber); + + // Add it to the target. + lineNumberTableAttributeEditor.addLineNumberInfo(newLineNumberInfo); + } +} diff --git a/src/proguard/classfile/editor/LineNumberTableAttributeEditor.java b/src/proguard/classfile/editor/LineNumberTableAttributeEditor.java new file mode 100644 index 0000000..ab96b38 --- /dev/null +++ b/src/proguard/classfile/editor/LineNumberTableAttributeEditor.java @@ -0,0 +1,67 @@ +/* + * 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.attribute.*; + +/** + * This class can add line numbers to a given line number table attribute. + * Line numbers to be added must have been filled out beforehand. + * + * @author Eric Lafortune + */ +public class LineNumberTableAttributeEditor +{ + private LineNumberTableAttribute targetLineNumberTableAttribute; + + + /** + * Creates a new LineNumberTableAttributeEditor that will edit line numbers + * in the given line number table attribute. + */ + public LineNumberTableAttributeEditor(LineNumberTableAttribute targetLineNumberTableAttribute) + { + this.targetLineNumberTableAttribute = targetLineNumberTableAttribute; + } + + + /** + * Adds a given line number to the line number table attribute. + */ + public void addLineNumberInfo(LineNumberInfo lineNumberInfo) + { + int lineNumberTableLength = targetLineNumberTableAttribute.u2lineNumberTableLength; + LineNumberInfo[] lineNumberTable = targetLineNumberTableAttribute.lineNumberTable; + + // Make sure there is enough space for the new lineNumberInfo. + if (lineNumberTable.length <= lineNumberTableLength) + { + targetLineNumberTableAttribute.lineNumberTable = new LineNumberInfo[lineNumberTableLength+1]; + System.arraycopy(lineNumberTable, 0, + targetLineNumberTableAttribute.lineNumberTable, 0, + lineNumberTableLength); + lineNumberTable = targetLineNumberTableAttribute.lineNumberTable; + } + + // Add the lineNumberInfo. + lineNumberTable[targetLineNumberTableAttribute.u2lineNumberTableLength++] = lineNumberInfo; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/LocalVariableInfoAdder.java b/src/proguard/classfile/editor/LocalVariableInfoAdder.java new file mode 100644 index 0000000..f285e4f --- /dev/null +++ b/src/proguard/classfile/editor/LocalVariableInfoAdder.java @@ -0,0 +1,67 @@ +/* + * 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.attribute.visitor.LocalVariableInfoVisitor; +import proguard.classfile.attribute.*; +import proguard.classfile.*; + +/** + * This LocalVariableInfoVisitor adds all line numbers that it visits to the given + * target line number attribute. + */ +public class LocalVariableInfoAdder +implements LocalVariableInfoVisitor +{ + private final ConstantAdder constantAdder; + private final LocalVariableTableAttributeEditor localVariableTableAttributeEditor; + + + /** + * Creates a new LocalVariableInfoAdder that will copy line numbers into the + * given target line number table. + */ + public LocalVariableInfoAdder(ProgramClass targetClass, + LocalVariableTableAttribute targetLocalVariableTableAttribute) + { + this.constantAdder = new ConstantAdder(targetClass); + this.localVariableTableAttributeEditor = new LocalVariableTableAttributeEditor(targetLocalVariableTableAttribute); + } + + + // Implementations for LocalVariableInfoVisitor. + + public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) + { + // Create a new line number. + LocalVariableInfo newLocalVariableInfo = + new LocalVariableInfo(localVariableInfo.u2startPC, + localVariableInfo.u2length, + constantAdder.addConstant(clazz, localVariableInfo.u2nameIndex), + constantAdder.addConstant(clazz, localVariableInfo.u2descriptorIndex), + localVariableInfo.u2index); + + newLocalVariableInfo.referencedClass = localVariableInfo.referencedClass; + + // Add it to the target. + localVariableTableAttributeEditor.addLocalVariableInfo(newLocalVariableInfo); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java b/src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java new file mode 100644 index 0000000..053b628 --- /dev/null +++ b/src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java @@ -0,0 +1,67 @@ +/* + * 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.attribute.*; + +/** + * This class can add local variables to a given local variable table attribute. + * Local variables to be added must have been filled out beforehand. + * + * @author Eric Lafortune + */ +public class LocalVariableTableAttributeEditor +{ + private LocalVariableTableAttribute targetLocalVariableTableAttribute; + + + /** + * Creates a new LocalVariableTableAttributeEditor that will edit line numbers + * in the given line number table attribute. + */ + public LocalVariableTableAttributeEditor(LocalVariableTableAttribute targetLocalVariableTableAttribute) + { + this.targetLocalVariableTableAttribute = targetLocalVariableTableAttribute; + } + + + /** + * Adds a given line number to the line number table attribute. + */ + public void addLocalVariableInfo(LocalVariableInfo localVariableInfo) + { + int localVariableTableLength = targetLocalVariableTableAttribute.u2localVariableTableLength; + LocalVariableInfo[] localVariableTable = targetLocalVariableTableAttribute.localVariableTable; + + // Make sure there is enough space for the new localVariableInfo. + if (localVariableTable.length <= localVariableTableLength) + { + targetLocalVariableTableAttribute.localVariableTable = new LocalVariableInfo[localVariableTableLength+1]; + System.arraycopy(localVariableTable, 0, + targetLocalVariableTableAttribute.localVariableTable, 0, + localVariableTableLength); + localVariableTable = targetLocalVariableTableAttribute.localVariableTable; + } + + // Add the localVariableInfo. + localVariableTable[targetLocalVariableTableAttribute.u2localVariableTableLength++] = localVariableInfo; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java b/src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java new file mode 100644 index 0000000..ca50f3f --- /dev/null +++ b/src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java @@ -0,0 +1,68 @@ +/* + * 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.attribute.visitor.LocalVariableTypeInfoVisitor; +import proguard.classfile.attribute.*; +import proguard.classfile.*; + +/** + * This LocalVariableTypeInfoVisitor adds all line numbers that it visits to the given + * target line number attribute. + */ +public class LocalVariableTypeInfoAdder +implements LocalVariableTypeInfoVisitor +{ + private final ConstantAdder constantAdder; + private final LocalVariableTypeTableAttributeEditor localVariableTypeTableAttributeEditor; + + + /** + * Creates a new LocalVariableTypeInfoAdder that will copy line numbers into the + * given target line number table. + */ + public LocalVariableTypeInfoAdder(ProgramClass targetClass, + LocalVariableTypeTableAttribute targetLocalVariableTypeTableAttribute) + { + this.constantAdder = new ConstantAdder(targetClass); + this.localVariableTypeTableAttributeEditor = new LocalVariableTypeTableAttributeEditor(targetLocalVariableTypeTableAttribute); + } + + + // Implementations for LocalVariableTypeInfoVisitor. + + public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) + { + // Create a new line number. + LocalVariableTypeInfo newLocalVariableTypeInfo = + new LocalVariableTypeInfo(localVariableTypeInfo.u2startPC, + localVariableTypeInfo.u2length, + constantAdder.addConstant(clazz, localVariableTypeInfo.u2nameIndex), + constantAdder.addConstant(clazz, localVariableTypeInfo.u2signatureIndex), + localVariableTypeInfo.u2index); + + // TODO: Clone array. + newLocalVariableTypeInfo.referencedClasses = localVariableTypeInfo.referencedClasses; + + // Add it to the target. + localVariableTypeTableAttributeEditor.addLocalVariableTypeInfo(newLocalVariableTypeInfo); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java b/src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java new file mode 100644 index 0000000..fe5a64d --- /dev/null +++ b/src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java @@ -0,0 +1,68 @@ +/* + * 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.attribute.*; + +/** + * This class can add local variables to a given local variable type table + * attribute. + * Local variable types to be added must have been filled out beforehand. + * + * @author Eric Lafortune + */ +public class LocalVariableTypeTableAttributeEditor +{ + private LocalVariableTypeTableAttribute targetLocalVariableTypeTableAttribute; + + + /** + * Creates a new LocalVariableTypeTableAttributeEditor that will edit line numbers + * in the given line number table attribute. + */ + public LocalVariableTypeTableAttributeEditor(LocalVariableTypeTableAttribute targetLocalVariableTypeTableAttribute) + { + this.targetLocalVariableTypeTableAttribute = targetLocalVariableTypeTableAttribute; + } + + + /** + * Adds a given line number to the line number table attribute. + */ + public void addLocalVariableTypeInfo(LocalVariableTypeInfo localVariableTypeInfo) + { + int localVariableTypeTableLength = targetLocalVariableTypeTableAttribute.u2localVariableTypeTableLength; + LocalVariableTypeInfo[] localVariableTypeTable = targetLocalVariableTypeTableAttribute.localVariableTypeTable; + + // Make sure there is enough space for the new localVariableTypeInfo. + if (localVariableTypeTable.length <= localVariableTypeTableLength) + { + targetLocalVariableTypeTableAttribute.localVariableTypeTable = new LocalVariableTypeInfo[localVariableTypeTableLength+1]; + System.arraycopy(localVariableTypeTable, 0, + targetLocalVariableTypeTableAttribute.localVariableTypeTable, 0, + localVariableTypeTableLength); + localVariableTypeTable = targetLocalVariableTypeTableAttribute.localVariableTypeTable; + } + + // Add the localVariableTypeInfo. + localVariableTypeTable[targetLocalVariableTypeTableAttribute.u2localVariableTypeTableLength++] = localVariableTypeInfo; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/MemberAdder.java b/src/proguard/classfile/editor/MemberAdder.java new file mode 100644 index 0000000..5f939bb --- /dev/null +++ b/src/proguard/classfile/editor/MemberAdder.java @@ -0,0 +1,257 @@ +/* + * 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.Attribute; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.classfile.visitor.MemberVisitor; + +/** + * This ConstantVisitor adds all class members that it visits to the given + * target class. + * + * @author Eric Lafortune + */ +public class MemberAdder +extends SimplifiedVisitor +implements MemberVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = true; + //*/ + + + private static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0]; + + + private final ProgramClass targetClass; +// private final boolean addFields; + + private final ConstantAdder constantAdder; + private final ClassEditor classEditor; + private final ConstantPoolEditor constantPoolEditor; + + + /** + * Creates a new MemberAdder that will copy methods into the given target + * class. + * @param targetClass the class to which all visited class members will be + * added. + */ +// * @param addFields specifies whether fields should be added, or fused +// * with the present fields. + public MemberAdder(ProgramClass targetClass)//), +// boolean addFields) + { + this.targetClass = targetClass; +// this.addFields = addFields; + + constantAdder = new ConstantAdder(targetClass); + classEditor = new ClassEditor(targetClass); + constantPoolEditor = new ConstantPoolEditor(targetClass); + } + + + // Implementations for MemberVisitor. + + public void visitProgramField(ProgramClass programClass, ProgramField programField) + { + String name = programField.getName(programClass); + String descriptor = programField.getDescriptor(programClass); + int accessFlags = programField.getAccessFlags(); + + // Does the target class already have such a field? + ProgramField targetField = (ProgramField)targetClass.findField(name, descriptor); + if (targetField != null) + { + // Is the field private or static? + int targetAccessFlags = targetField.getAccessFlags(); + if ((targetAccessFlags & + (ClassConstants.INTERNAL_ACC_PRIVATE | + ClassConstants.INTERNAL_ACC_STATIC)) != 0) + { + if (DEBUG) + { + System.out.println("MemberAdder: renaming field ["+targetClass+"."+targetField.getName(targetClass)+" "+targetField.getDescriptor(targetClass)+"]"); + } + + // Rename the private or static field. + targetField.u2nameIndex = + constantPoolEditor.addUtf8Constant(newUniqueMemberName(name, targetClass.getName())); + } +// else +// { +// // Keep the non-private and non-static field, but update its +// // contents, in order to keep any references to it valid. +// if (DEBUG) +// { +// System.out.println("MemberAdder: updating field ["+programClass+"."+programField.getName(programClass)+" "+programField.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]"); +// } +// +// // Combine the access flags. +// targetField.u2accessFlags = accessFlags | targetAccessFlags; +// +// // Add and replace any attributes. +// programField.attributesAccept(programClass, +// new AttributeAdder(targetClass, +// targetField, +// true)); +// +// // Don't add a new field. +// return; +// } + } + + if (DEBUG) + { + System.out.println("MemberAdder: copying field ["+programClass+"."+programField.getName(programClass)+" "+programField.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]"); + } + + // Create a copy of the field. + ProgramField newProgramField = + new ProgramField(accessFlags, + constantAdder.addConstant(programClass, programField.u2nameIndex), + constantAdder.addConstant(programClass, programField.u2descriptorIndex), + 0, + programField.u2attributesCount > 0 ? + new Attribute[programField.u2attributesCount] : + EMPTY_ATTRIBUTES, + programField.referencedClass); + + // Link to its visitor info. + newProgramField.setVisitorInfo(programField); + + // Copy its attributes. + programField.attributesAccept(programClass, + new AttributeAdder(targetClass, + newProgramField, + false)); + + // Add the completed field. + classEditor.addField(newProgramField); + } + + + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { + String name = programMethod.getName(programClass); + String descriptor = programMethod.getDescriptor(programClass); + int accessFlags = programMethod.getAccessFlags(); + + // Does the target class already have such a method? + ProgramMethod targetMethod = (ProgramMethod)targetClass.findMethod(name, descriptor); + if (targetMethod != null) + { + // is this source method abstract? + if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0) + { + // Keep the target method. + if (DEBUG) + { + System.out.println("MemberAdder: skipping abstract method ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]"); + } + + // Don't add a new method. + return; + } + + // Is the target method abstract? + int targetAccessFlags = targetMethod.getAccessFlags(); + if ((targetAccessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0) + { + // Keep the abstract method, but update its contents, in order + // to keep any references to it valid. + if (DEBUG) + { + System.out.println("MemberAdder: updating method ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]"); + } + + // Replace the access flags. + targetMethod.u2accessFlags = + accessFlags & ~ClassConstants.INTERNAL_ACC_FINAL; + + // Add and replace the attributes. + programMethod.attributesAccept(programClass, + new AttributeAdder(targetClass, + targetMethod, + true)); + + // Don't add a new method. + return; + } + + if (DEBUG) + { + System.out.println("MemberAdder: renaming method ["+targetClass.getName()+"."+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+"]"); + } + + // Rename the private (non-abstract) or static method. + targetMethod.u2nameIndex = + constantPoolEditor.addUtf8Constant(newUniqueMemberName(name, descriptor)); + } + + if (DEBUG) + { + System.out.println("MemberAdder: copying method ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]"); + } + + // Create a copy of the method. + ProgramMethod newProgramMethod = + new ProgramMethod(accessFlags & ~ClassConstants.INTERNAL_ACC_FINAL, + constantAdder.addConstant(programClass, programMethod.u2nameIndex), + constantAdder.addConstant(programClass, programMethod.u2descriptorIndex), + 0, + programMethod.u2attributesCount > 0 ? + new Attribute[programMethod.u2attributesCount] : + EMPTY_ATTRIBUTES, + programMethod.referencedClasses != null ? + (Clazz[])programMethod.referencedClasses.clone() : + null); + + // Link to its visitor info. + newProgramMethod.setVisitorInfo(programMethod); + + // Copy its attributes. + programMethod.attributesAccept(programClass, + new AttributeAdder(targetClass, + newProgramMethod, + false)); + + // Add the completed method. + classEditor.addMethod(newProgramMethod); + } + + + // Small utility methods. + + /** + * 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())); + } +} diff --git a/src/proguard/classfile/editor/MemberReferenceFixer.java b/src/proguard/classfile/editor/MemberReferenceFixer.java new file mode 100644 index 0000000..4bd8af5 --- /dev/null +++ b/src/proguard/classfile/editor/MemberReferenceFixer.java @@ -0,0 +1,456 @@ +/* + * 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.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 class? + if (!enclosingMethodAttribute.getClassName(clazz).equals(referencedClass.getName())) + { + // Update the class index. + enclosingMethodAttribute.u2classIndex = + new ConstantPoolEditor((ProgramClass)clazz).addClassConstant(referencedClass); + } + + // Does it have a new name or type? + if (!enclosingMethodAttribute.getName(clazz).equals(referencedMember.getName(referencedClass)) || + !enclosingMethodAttribute.getType(clazz).equals(referencedMember.getDescriptor(referencedClass))) + { + // Update the name and type index. + enclosingMethodAttribute.u2nameAndTypeIndex = + new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(referencedMember.getName(referencedClass), + referencedMember.getDescriptor(referencedClass)); + } + } + } + + + 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(" Class file = "+clazz.getName()); + System.out.println(" Ref class = "+referencedClass.getName()); + System.out.println(" Ref member name = "+stringConstant.getString(clazz)); + System.out.println(" -> "+referencedMember.getName(referencedClass)); + } + + + private void debug(Clazz clazz, + RefConstant refConstant, + Clazz referencedClass, + Member referencedMember) + { + System.out.println("MemberReferenceFixer:"); + System.out.println(" Class file = "+clazz.getName()); + System.out.println(" Ref class = "+referencedClass.getName()); + System.out.println(" Ref member name = "+refConstant.getName(clazz)); + System.out.println(" -> "+referencedMember.getName(referencedClass)); + System.out.println(" Ref descriptor = "+refConstant.getType(clazz)); + System.out.println(" -> "+referencedMember.getDescriptor(referencedClass)); + } +} diff --git a/src/proguard/classfile/editor/MethodInvocationFixer.java b/src/proguard/classfile/editor/MethodInvocationFixer.java new file mode 100644 index 0000000..ef76012 --- /dev/null +++ b/src/proguard/classfile/editor/MethodInvocationFixer.java @@ -0,0 +1,254 @@ +/* + * 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.visitor.AttributeVisitor; +import proguard.classfile.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.*; +import proguard.classfile.visitor.*; + +/** + * This AttributeVisitor fixes all inappropriate special/virtual/static/interface + * invocations of the code attributes that it visits. + * + * @author Eric Lafortune + */ +public class MethodInvocationFixer +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor, + ConstantVisitor, + ClassVisitor, + MemberVisitor +{ + private static final boolean DEBUG = false; + + + private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + + // Return values for the visitor methods. + private Clazz referencedClass; + private Clazz referencedMethodClass; + private Member referencedMethod; + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Reset the code attribute editor. + codeAttributeEditor.reset(codeAttribute.u4codeLength); + + // Remap the variables of the instructions. + codeAttribute.instructionsAccept(clazz, method, this); + + // Apply the code atribute editor. + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + + + // Implementations for InstructionVisitor. + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + int constantIndex = constantInstruction.constantIndex; + + // Get information on the called class and method, if present. + referencedMethod = null; + + clazz.constantPoolEntryAccept(constantIndex, this); + + // Did we find the called class and method? + if (referencedMethod != null) + { + // Do we need to update the opcode? + byte opcode = constantInstruction.opcode; + + // Is the method static? + if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0) + { + // But is it not a static invocation? + if (opcode != InstructionConstants.OP_INVOKESTATIC) + { + // Replace the invocation by an invokestatic instruction. + Instruction replacementInstruction = + new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, + constantIndex).shrink(); + + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); + + if (DEBUG) + { + debug(clazz, method, offset, constantInstruction, replacementInstruction); + } + } + } + + // Is the method private, or an instance initializer? + else if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_PRIVATE) != 0 || + referencedMethod.getName(referencedMethodClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)) + { + // But is it not a special invocation? + if (opcode != InstructionConstants.OP_INVOKESPECIAL) + { + // Replace the invocation by an invokespecial instruction. + Instruction replacementInstruction = + new ConstantInstruction(InstructionConstants.OP_INVOKESPECIAL, + constantIndex).shrink(); + + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); + + if (DEBUG) + { + debug(clazz, method, offset, constantInstruction, replacementInstruction); + } + } + } + + // Is the method an interface method? + else if ((referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0) + { + int invokeinterfaceConstant = + (ClassUtil.internalMethodParameterSize(referencedMethod.getDescriptor(referencedMethodClass), false)) << 8; + + // But is it not an interface invocation, or is the parameter + // size incorrect? + if (opcode != InstructionConstants.OP_INVOKEINTERFACE || + constantInstruction.constant != invokeinterfaceConstant) + { + // Fix the parameter size of the interface invocation. + Instruction replacementInstruction = + new ConstantInstruction(InstructionConstants.OP_INVOKEINTERFACE, + constantIndex, + invokeinterfaceConstant).shrink(); + + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); + + if (DEBUG) + { + debug(clazz, method, offset, constantInstruction, replacementInstruction); + } + } + } + + // The method is not static, private, an instance initializer, or + // an interface method. + else + { + // But is it not a virtual invocation (or a special invocation, + // but not a super call)? + if (opcode != InstructionConstants.OP_INVOKEVIRTUAL && + (opcode != InstructionConstants.OP_INVOKESPECIAL || + !clazz.extends_(referencedClass))) + { + // Replace the invocation by an invokevirtual instruction. + Instruction replacementInstruction = + new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, + constantIndex).shrink(); + + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); + + if (DEBUG) + { + debug(clazz, method, offset, constantInstruction, replacementInstruction); + } + } + } + } + } + + + // Implementations for ConstantVisitor. + + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + + public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant) + { + // Check if this is an interface method. Note that we're interested in + // the class of the method reference, not in the class in which the + // method was actually found. + //refConstant.referencedClassAccept(this); + clazz.constantPoolEntryAccept(refConstant.u2classIndex, this); + + // Get the referenced access flags and names. + refConstant.referencedMemberAccept(this); + } + + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + // Check if this is an interface class. + classConstant.referencedClassAccept(this); + } + + + // Implementations for ClassVisitor. + + public void visitAnyClass(Clazz clazz) + { + // Remember the referenced class. + referencedClass = clazz; + } + + + // Implementations for MemberVisitor. + + public void visitAnyMember(Clazz clazz, Member member) + { + // Remember the referenced method. + referencedMethodClass = clazz; + referencedMethod = member; + } + + + // Small utility methods. + + private void debug(Clazz clazz, + Method method, + int offset, + ConstantInstruction constantInstruction, + Instruction replacementInstruction) + { + System.out.println("MethodInvocationFixer:"); + System.out.println(" Class = "+clazz.getName()); + System.out.println(" Method = "+method.getName(clazz)+method.getDescriptor(clazz)); + System.out.println(" Instruction = "+constantInstruction.toString(offset)); + System.out.println(" -> Class = "+referencedClass); + System.out.println(" Method = "+referencedMethod); + if ((referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0) + { + System.out.println(" Parameter size = "+(ClassUtil.internalMethodParameterSize(referencedMethod.getDescriptor(referencedMethodClass), false))); + } + System.out.println(" Replacement instruction = "+replacementInstruction.toString(offset)); + } +} diff --git a/src/proguard/classfile/editor/NamedAttributeDeleter.java b/src/proguard/classfile/editor/NamedAttributeDeleter.java new file mode 100644 index 0000000..0c4d339 --- /dev/null +++ b/src/proguard/classfile/editor/NamedAttributeDeleter.java @@ -0,0 +1,54 @@ +/* + * 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.visitor.*; +import proguard.util.StringMatcher; + + +/** + * This ClassVisitor deletes attributes with a given name in the program + * classes that it visits. + * + * @author Eric Lafortune + */ +public class NamedAttributeDeleter implements ClassVisitor +{ + private final String attributeName; + + + public NamedAttributeDeleter(String attributeName) + { + this.attributeName = attributeName; + } + + + // Implementations for ClassVisitor. + + public void visitLibraryClass(LibraryClass libraryClass) {} + + + public void visitProgramClass(ProgramClass programClass) + { + new AttributesEditor(programClass, false).deleteAttribute(attributeName); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java b/src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java new file mode 100644 index 0000000..4cad6b8 --- /dev/null +++ b/src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java @@ -0,0 +1,71 @@ +/* + * 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.attribute.annotation.*; + +/** + * This class can add annotations to a given parameter annotations attribute. + * Annotations to be added must have been filled out beforehand. + * + * @author Eric Lafortune + */ +public class ParameterAnnotationsAttributeEditor +{ + private ParameterAnnotationsAttribute targetParameterAnnotationsAttribute; + + + /** + * Creates a new ParameterAnnotationsAttributeEditor that will edit + * annotations in the given parameter annotations attribute. + */ + public ParameterAnnotationsAttributeEditor(ParameterAnnotationsAttribute targetParameterAnnotationsAttribute) + { + this.targetParameterAnnotationsAttribute = targetParameterAnnotationsAttribute; + } + + + /** + * Adds a given annotation to the annotations attribute. + */ + public void addAnnotation(int parameterIndex, Annotation annotation) + { + int annotationsCount = targetParameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]; + Annotation[] annotations = targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex]; + + // Make sure there is enough space for the new annotation. + if (annotations == null || + annotations.length <= annotationsCount) + { + targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex] = new Annotation[annotationsCount+1]; + if (annotations != null) + { + System.arraycopy(annotations, 0, + targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex], 0, + annotationsCount); + } + annotations = targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex]; + } + + // Add the annotation. + annotations[targetParameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]++] = annotation; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/StackSizeUpdater.java b/src/proguard/classfile/editor/StackSizeUpdater.java new file mode 100644 index 0000000..94e0519 --- /dev/null +++ b/src/proguard/classfile/editor/StackSizeUpdater.java @@ -0,0 +1,54 @@ +/* + * 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.visitor.*; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AttributeVisitor computes and updates the maximum stack size of the + * code attributes that it visits. + * + * @author Eric Lafortune + */ +public class StackSizeUpdater +extends SimplifiedVisitor +implements AttributeVisitor +{ + private final StackSizeComputer stackSizeComputer = new StackSizeComputer(); + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Compute the stack sizes. + stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute); + + // Update the maximum stack size. + codeAttribute.u2maxStack = stackSizeComputer.getMaxStackSize(); + } +} diff --git a/src/proguard/classfile/editor/SubclassAdder.java b/src/proguard/classfile/editor/SubclassAdder.java new file mode 100644 index 0000000..6b9fd64 --- /dev/null +++ b/src/proguard/classfile/editor/SubclassAdder.java @@ -0,0 +1,59 @@ +/* + * 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.visitor.ClassVisitor; + +/** + * This ClassVisitor adds the given class to the list of subclasses of the + * classes that it visits. + * + * @author Eric Lafortune + */ +public class SubclassAdder +implements ClassVisitor +{ + private final Clazz subclass; + + + /** + * Creates a new SubclassAdder that will add the given subclass. + */ + public SubclassAdder(Clazz subclass) + { + this.subclass = subclass; + } + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + programClass.addSubClass(subclass); + } + + + public void visitLibraryClass(LibraryClass libraryClass) + { + libraryClass.addSubClass(subclass); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/SubclassToAdder.java b/src/proguard/classfile/editor/SubclassToAdder.java new file mode 100644 index 0000000..deb242f --- /dev/null +++ b/src/proguard/classfile/editor/SubclassToAdder.java @@ -0,0 +1,60 @@ +/* + * 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.visitor.ClassVisitor; + +/** + * This ClassVisitor adds all classes that it visits to the list of subclasses + * of the given target class. + * + * @author Eric Lafortune + */ +public class SubclassToAdder +implements ClassVisitor +{ + private final Clazz targetClass; + + + /** + * Creates a new SubclassAdder that will add subclasses to the given + * target class. + */ + public SubclassToAdder(Clazz targetClass) + { + this.targetClass = targetClass; + } + + + // Implementations for ClassVisitor. + + public void visitProgramClass(ProgramClass programClass) + { + targetClass.addSubClass(programClass); + } + + + public void visitLibraryClass(LibraryClass libraryClass) + { + targetClass.addSubClass(libraryClass); + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/VariableCleaner.java b/src/proguard/classfile/editor/VariableCleaner.java new file mode 100644 index 0000000..1e93c15 --- /dev/null +++ b/src/proguard/classfile/editor/VariableCleaner.java @@ -0,0 +1,135 @@ +/* + * 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.visitor.*; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; +import proguard.optimize.info.VariableUsageMarker; + +/** + * This AttributeVisitor cleans up unused variables in all attributes that it + * visits. + * + * @author Eric Lafortune + */ +public class VariableCleaner +extends SimplifiedVisitor +implements AttributeVisitor +{ + private final VariableUsageMarker variableUsageMarker = new VariableUsageMarker(); + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Figure out the local variables that are used by the code. + variableUsageMarker.visitCodeAttribute(clazz, method, codeAttribute); + + // Clean up the variables of the attributes. + codeAttribute.attributesAccept(clazz, method, this); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + // Clean up local variables that aren't used. + localVariableTableAttribute.u2localVariableTableLength = + removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable, + localVariableTableAttribute.u2localVariableTableLength, + codeAttribute.u2maxLocals); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + // Clean up local variables that aren't used. + localVariableTypeTableAttribute.u2localVariableTypeTableLength = + removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable, + localVariableTypeTableAttribute.u2localVariableTypeTableLength, + codeAttribute.u2maxLocals); + } + + + // Small utility methods. + + /** + * Returns the given list of local variables, without the ones that aren't + * used + */ + private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos, + int localVariableInfoCount, + int maxLocals) + { + // Overwrite all empty local variable entries. + int newIndex = 0; + for (int index = 0; index < localVariableInfoCount && index < maxLocals; index++) + { + if (variableUsageMarker.isVariableUsed(index)) + { + localVariableInfos[newIndex++] = localVariableInfos[index]; + } + } + + // Clean up any remaining array elements. + for (int index = newIndex; index < localVariableInfoCount; index++) + { + localVariableInfos[index] = null; + } + + return newIndex; + } + + + /** + * Returns the given list of local variable types, without the ones that + * aren't used + */ + private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos, + int localVariableTypeInfoCount, + int maxLocals) + { + // Overwrite all empty local variable type entries. + int newIndex = 0; + for (int index = 0; index < localVariableTypeInfoCount && index < maxLocals; index++) + { + if (variableUsageMarker.isVariableUsed(index)) + { + localVariableTypeInfos[newIndex++] = localVariableTypeInfos[index]; + } + } + + // Clean up any remaining array elements. + for (int index = newIndex; index < localVariableTypeInfoCount; index++) + { + localVariableTypeInfos[index] = null; + } + + return newIndex; + } +} \ No newline at end of file diff --git a/src/proguard/classfile/editor/VariableEditor.java b/src/proguard/classfile/editor/VariableEditor.java new file mode 100644 index 0000000..a583b49 --- /dev/null +++ b/src/proguard/classfile/editor/VariableEditor.java @@ -0,0 +1,129 @@ +/* + * 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.visitor.AttributeVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AttributeVisitor accumulates specified changes to local variables, and + * then applies these accumulated changes to the code attributes that it visits. + * + * @author Eric Lafortune + */ +public class VariableEditor +extends SimplifiedVisitor +implements AttributeVisitor +{ + private boolean modified; + + private boolean[] deleted = new boolean[ClassConstants.TYPICAL_VARIABLES_SIZE]; + private int[] variableMap = new int[ClassConstants.TYPICAL_VARIABLES_SIZE]; + + private final VariableRemapper variableRemapper = new VariableRemapper(); + + + /** + * Resets the accumulated code changes. + * @param maxLocals the length of the local variable frame that will be + * edited next. + */ + public void reset(int maxLocals) + { + // Try to reuse the previous array. + if (deleted.length < maxLocals) + { + deleted = new boolean[maxLocals]; + } + else + { + for (int index = 0; index < maxLocals; index++) + { + deleted[index] = false; + } + } + + modified = false; + } + + + /** + * Remembers to delete the given variable. + * @param variableIndex the index of the variable to be deleted. + */ + public void deleteVariable(int variableIndex) + { + deleted[variableIndex] = true; + + modified = true; + } + + + /** + * Returns whether the given variable at the given offset has deleted. + */ + public boolean isDeleted(int instructionOffset) + { + return deleted[instructionOffset]; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Avoid doing any work if nothing is changing anyway. + if (!modified) + { + return; + } + + int oldMaxLocals = codeAttribute.u2maxLocals; + + // Make sure there is a sufficiently large variable map. + if (variableMap.length < oldMaxLocals) + { + variableMap = new int[oldMaxLocals]; + } + + // Fill out the variable map. + int newVariableIndex = 0; + for (int oldVariableIndex = 0; oldVariableIndex < oldMaxLocals; oldVariableIndex++) + { + variableMap[oldVariableIndex] = deleted[oldVariableIndex] ? + -1 : newVariableIndex++; + } + + // Set the map. + variableRemapper.setVariableMap(variableMap); + + // Remap the variables. + variableRemapper.visitCodeAttribute(clazz, method, codeAttribute); + + // Update the length of local variable frame. + codeAttribute.u2maxLocals = newVariableIndex; + } +} diff --git a/src/proguard/classfile/editor/VariableRemapper.java b/src/proguard/classfile/editor/VariableRemapper.java new file mode 100644 index 0000000..590cd4e --- /dev/null +++ b/src/proguard/classfile/editor/VariableRemapper.java @@ -0,0 +1,197 @@ +/* + * 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.visitor.*; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.SimplifiedVisitor; + +/** + * This AttributeVisitor remaps variable indexes in all attributes that it + * visits, based on a given index map. + * + * @author Eric Lafortune + */ +public class VariableRemapper +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor, + LocalVariableInfoVisitor, + LocalVariableTypeInfoVisitor +{ + private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + + private int[] variableMap; + + + /** + * Sets the given mapping of old variable indexes to their new indexes. + * Variables that should disappear can be mapped to -1. + */ + public void setVariableMap(int[] variableMap) + { + this.variableMap = variableMap; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // Initially, the code attribute editor doesn't contain any changes. + codeAttributeEditor.reset(codeAttribute.u4codeLength); + + // Remap the variables of the instructions. + codeAttribute.instructionsAccept(clazz, method, this); + + // Apply the code atribute editor. + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + + // Remap the variables of the attributes. + codeAttribute.attributesAccept(clazz, method, this); + } + + + public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) + { + // Remap the variable references of the local variables. + localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + + // Remove local variables that haven't been mapped. + localVariableTableAttribute.u2localVariableTableLength = + removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable, + localVariableTableAttribute.u2localVariableTableLength); + } + + + public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) + { + // Remap the variable references of the local variables. + localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); + + // Remove local variables that haven't been mapped. + localVariableTypeTableAttribute.u2localVariableTypeTableLength = + removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable, + localVariableTypeTableAttribute.u2localVariableTypeTableLength); + } + + + // Implementations for LocalVariableInfoVisitor. + + public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) + { + localVariableInfo.u2index = + remapVariable(localVariableInfo.u2index); + } + + + // Implementations for LocalVariableTypeInfoVisitor. + + public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) + { + localVariableTypeInfo.u2index = + remapVariable(localVariableTypeInfo.u2index); + } + + + // Implementations for InstructionVisitor. + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) + { + // Is the new variable index different from the original one? + int oldVariableIndex = variableInstruction.variableIndex; + int newVariableIndex = remapVariable(oldVariableIndex); + if (newVariableIndex != oldVariableIndex) + { + // Replace the instruction. + Instruction replacementInstruction = + new VariableInstruction(variableInstruction.opcode, + newVariableIndex, + variableInstruction.constant).shrink(); + + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); + } + } + + + // Small utility methods. + + /** + * Returns the new variable index of the given variable. + */ + private int remapVariable(int variableIndex) + { + return variableMap[variableIndex]; + } + + + /** + * Returns the given list of local variables, without the ones that have + * been removed. + */ + private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos, + int localVariableInfoCount) + { + // Overwrite all empty local variable entries. + int newIndex = 0; + for (int index = 0; index < localVariableInfoCount; index++) + { + LocalVariableInfo localVariableInfo = localVariableInfos[index]; + if (localVariableInfo.u2index >= 0) + { + localVariableInfos[newIndex++] = localVariableInfo; + } + } + + return newIndex; + } + + + /** + * Returns the given list of local variable types, without the ones that + * have been removed. + */ + private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos, + int localVariableTypeInfoCount) + { + // Overwrite all empty local variable type entries. + int newIndex = 0; + for (int index = 0; index < localVariableTypeInfoCount; index++) + { + LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index]; + if (localVariableTypeInfo.u2index >= 0) + { + localVariableTypeInfos[newIndex++] = localVariableTypeInfo; + } + } + + return newIndex; + } +} diff --git a/src/proguard/classfile/editor/VariableSizeUpdater.java b/src/proguard/classfile/editor/VariableSizeUpdater.java new file mode 100644 index 0000000..18958c5 --- /dev/null +++ b/src/proguard/classfile/editor/VariableSizeUpdater.java @@ -0,0 +1,98 @@ +/* + * 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.visitor.AttributeVisitor; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.*; + +/** + * This AttributeVisitor computes and updates the maximum local variable frame + * size of the code attributes that it visits. + * + * @author Eric Lafortune + */ +public class VariableSizeUpdater +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor +{ + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = true; + //*/ + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { +// DEBUG = +// clazz.getName().equals("abc/Def") && +// method.getName(clazz).equals("abc"); + + // The minimum variable size is determined by the arguments. + codeAttribute.u2maxLocals = + ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), + method.getAccessFlags()); + + if (DEBUG) + { + System.out.println("VariableSizeUpdater: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)); + System.out.println(" Max locals: "+codeAttribute.u2maxLocals+" <- parameters"); + } + + // Go over all instructions. + codeAttribute.instructionsAccept(clazz, method, this); + } + + + // Implementations for InstructionVisitor. + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) + { + int variableSize = variableInstruction.variableIndex + 1; + if (variableInstruction.isCategory2()) + { + variableSize++; + } + + if (codeAttribute.u2maxLocals < variableSize) + { + codeAttribute.u2maxLocals = variableSize; + + if (DEBUG) + { + System.out.println("Max locals: "+codeAttribute.u2maxLocals+" <- "+variableInstruction.toString(offset)); + } + } + } +} diff --git a/src/proguard/classfile/editor/package.html b/src/proguard/classfile/editor/package.html new file mode 100644 index 0000000..d37f541 --- /dev/null +++ b/src/proguard/classfile/editor/package.html @@ -0,0 +1,3 @@ + +This package contains visitors to edit byte code. + -- cgit v1.2.3