/* * 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.optimize.peephole; import proguard.classfile.*; import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.KeepMarker; import proguard.optimize.info.*; import proguard.util.*; import java.util.*; /** * This ClassVisitor inlines the classes that it visits in a given target class, * whenever possible. * * @see RetargetedInnerClassAttributeRemover * @see TargetClassChanger * @see ClassReferenceFixer * @see MemberReferenceFixer * @see AccessFixer * @author Eric Lafortune */ public class ClassMerger extends SimplifiedVisitor implements ClassVisitor, ConstantVisitor { //* private static final boolean DEBUG = false; /*/ private static boolean DEBUG = true; //*/ private final ProgramClass targetClass; private final boolean allowAccessModification; private final boolean mergeInterfacesAggressively; private final ClassVisitor extraClassVisitor; /** * Creates a new ClassMerger that will merge classes into the given target * class. * @param targetClass the class into which all visited * classes will be merged. * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may * be merged aggressively. */ public ClassMerger(ProgramClass targetClass, boolean allowAccessModification, boolean mergeInterfacesAggressively) { this(targetClass, allowAccessModification, mergeInterfacesAggressively, null); } /** * Creates a new ClassMerger that will merge classes into the given target * class. * @param targetClass the class into which all visited * classes will be merged. * @param allowAccessModification specifies whether the access modifiers * of classes can be changed in order to * merge them. * @param mergeInterfacesAggressively specifies whether interfaces may * be merged aggressively. * @param extraClassVisitor an optional extra visitor for all * merged classes. */ public ClassMerger(ProgramClass targetClass, boolean allowAccessModification, boolean mergeInterfacesAggressively, ClassVisitor extraClassVisitor) { this.targetClass = targetClass; this.allowAccessModification = allowAccessModification; this.mergeInterfacesAggressively = mergeInterfacesAggressively; this.extraClassVisitor = extraClassVisitor; } // Implementations for ClassVisitor. public void visitProgramClass(ProgramClass programClass) { //final String CLASS_NAME = "abc/Def"; //DEBUG = programClass.getName().equals(CLASS_NAME) || // targetClass.getName().equals(CLASS_NAME); // TODO: Remove this when the class merger has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { visitProgramClass0(programClass); } catch (RuntimeException ex) { System.err.println("Unexpected error while merging classes:"); System.err.println(" Class = ["+programClass.getName()+"]"); System.err.println(" Target class = ["+targetClass.getName()+"]"); System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); if (DEBUG) { programClass.accept(new ClassPrinter()); targetClass.accept(new ClassPrinter()); } throw ex; } } public void visitProgramClass0(ProgramClass programClass) { if (!programClass.equals(targetClass) && // Don't merge classes that must be preserved. !KeepMarker.isKept(programClass) && !KeepMarker.isKept(targetClass) && // Only merge classes that haven't been retargeted yet. getTargetClass(programClass) == null && getTargetClass(targetClass) == null && // Don't merge annotation classes, with all their introspection and // infinite recursion. (programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_ANNOTATTION) == 0 && // Only merge classes if we can change the access permissioms, or // if they are in the same package, or // if they are public and don't contain or invoke package visible // class members. (allowAccessModification || ((programClass.getAccessFlags() & targetClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_PUBLIC) != 0 && !PackageVisibleMemberContainingClassMarker.containsPackageVisibleMembers(programClass) && !PackageVisibleMemberInvokingClassMarker.invokesPackageVisibleMembers(programClass)) || ClassUtil.internalPackageName(programClass.getName()).equals( ClassUtil.internalPackageName(targetClass.getName()))) && // Only merge two classes or two interfaces or two abstract classes, // or a class into an interface with a single implementation. ((programClass.getAccessFlags() & (ClassConstants.INTERNAL_ACC_INTERFACE | ClassConstants.INTERNAL_ACC_ABSTRACT)) == (targetClass.getAccessFlags() & (ClassConstants.INTERNAL_ACC_INTERFACE | ClassConstants.INTERNAL_ACC_ABSTRACT)) || (isOnlySubClass(programClass, targetClass) && (programClass.getSuperClass().equals(targetClass) || programClass.getSuperClass().equals(targetClass.getSuperClass())))) && // One class must not implement the other class indirectly. !indirectlyImplementedInterfaces(programClass).contains(targetClass) && !targetClass.extendsOrImplements(programClass) && // The two classes must have the same superclasses and interfaces // with static initializers. initializedSuperClasses(programClass).equals(initializedSuperClasses(targetClass)) && // The two classes must have the same superclasses and interfaces // that are tested with 'instanceof'. instanceofedSuperClasses(programClass).equals(instanceofedSuperClasses(targetClass)) && // The two classes must have the same superclasses that are caught // as exceptions. caughtSuperClasses(programClass).equals(caughtSuperClasses(targetClass)) && // The two classes must not both be part of a .class construct. !(DotClassMarker.isDotClassed(programClass) && DotClassMarker.isDotClassed(targetClass)) && // The two classes must not introduce any unwanted fields. !introducesUnwantedFields(programClass, targetClass) && !introducesUnwantedFields(targetClass, programClass) && // The classes must not have clashing constructors. !haveAnyIdenticalInitializers(programClass, targetClass) && // The classes must not introduce abstract methods, unless // explicitly allowed. (mergeInterfacesAggressively || (!introducesUnwantedAbstractMethods(programClass, targetClass) && !introducesUnwantedAbstractMethods(targetClass, programClass))) && // The classes must not override each others concrete methods. !overridesAnyMethods(programClass, targetClass) && !overridesAnyMethods(targetClass, programClass) && // The classes must not shadow each others non-private methods. !shadowsAnyMethods(programClass, targetClass) && !shadowsAnyMethods(targetClass, programClass)) { if (DEBUG) { System.out.println("ClassMerger ["+programClass.getName()+"] -> ["+targetClass.getName()+"]"); System.out.println(" Source interface? ["+((programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]"); System.out.println(" Target interface? ["+((targetClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]"); System.out.println(" Source subclasses ["+programClass.subClasses+"]"); System.out.println(" Target subclasses ["+targetClass.subClasses+"]"); System.out.println(" Source superclass ["+programClass.getSuperClass().getName()+"]"); System.out.println(" Target superclass ["+targetClass.getSuperClass().getName()+"]"); } // Combine the access flags. int targetAccessFlags = targetClass.getAccessFlags(); int sourceAccessFlags = programClass.getAccessFlags(); targetClass.u2accessFlags = ((targetAccessFlags & sourceAccessFlags) & (ClassConstants.INTERNAL_ACC_INTERFACE | ClassConstants.INTERNAL_ACC_ABSTRACT)) | ((targetAccessFlags | sourceAccessFlags) & (ClassConstants.INTERNAL_ACC_PUBLIC | ClassConstants.INTERNAL_ACC_ANNOTATTION | ClassConstants.INTERNAL_ACC_ENUM)); // Copy over the superclass, unless it's the target class itself. //if (!targetClass.getName().equals(programClass.getSuperName())) //{ // targetClass.u2superClass = // new ConstantAdder(targetClass).addConstant(programClass, programClass.u2superClass); //} // Copy over the interfaces that aren't present yet and that // wouldn't cause loops in the class hierarchy. programClass.interfaceConstantsAccept( new ExceptClassConstantFilter(targetClass.getName(), new ImplementedClassConstantFilter(targetClass, new ImplementingClassConstantFilter(targetClass, new InterfaceAdder(targetClass))))); // Copy over the class members. MemberAdder memberAdder = new MemberAdder(targetClass); programClass.fieldsAccept(memberAdder); programClass.methodsAccept(memberAdder); // Copy over the other attributes. programClass.attributesAccept( new AttributeAdder(targetClass, true)); // Update the optimization information of the target class. ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(targetClass); if (info != null) { info.merge(ClassOptimizationInfo.getClassOptimizationInfo(programClass)); } // Remember to replace the inlined class by the target class. setTargetClass(programClass, targetClass); // Visit the merged class, if required. if (extraClassVisitor != null) { extraClassVisitor.visitProgramClass(programClass); } } } // Small utility methods. /** * Returns whether a given class is the only subclass of another given class. */ private boolean isOnlySubClass(Clazz subClass, ProgramClass clazz) { // TODO: The list of subclasses is not up to date. return clazz.subClasses != null && clazz.subClasses.length == 1 && clazz.subClasses[0].equals(subClass); } /** * Returns the set of indirectly implemented interfaces. */ private Set indirectlyImplementedInterfaces(Clazz clazz) { Set set = new HashSet(); ReferencedClassVisitor referencedInterfaceCollector = new ReferencedClassVisitor( new ClassHierarchyTraveler(false, false, true, false, new ClassCollector(set))); // Visit all superclasses and collect their interfaces. clazz.superClassConstantAccept(referencedInterfaceCollector); // Visit all interfaces and collect their interfaces. clazz.interfaceConstantsAccept(referencedInterfaceCollector); return set; } /** * Returns the set of superclasses and interfaces that are initialized. */ private Set initializedSuperClasses(Clazz clazz) { Set set = new HashSet(); // Visit all superclasses and interfaces, collecting the ones that have // static initializers. clazz.hierarchyAccept(true, true, true, false, new NamedMethodVisitor(ClassConstants.INTERNAL_METHOD_NAME_CLINIT, ClassConstants.INTERNAL_METHOD_TYPE_INIT, new MemberToClassVisitor( new ClassCollector(set)))); return set; } /** * Returns the set of superclasses and interfaces that are used in * 'instanceof' tests. */ private Set instanceofedSuperClasses(Clazz clazz) { Set set = new HashSet(); // Visit all superclasses and interfaces, collecting the ones that are // used in an 'instanceof' test. clazz.hierarchyAccept(true, true, true, false, new InstanceofClassFilter( new ClassCollector(set))); return set; } /** * Returns the set of superclasses that are caught as exceptions. */ private Set caughtSuperClasses(Clazz clazz) { Set set = new HashSet(); // Visit all superclasses, collecting the ones that are caught. clazz.hierarchyAccept(true, true, false, false, new CaughtClassFilter( new ClassCollector(set))); return set; } /** * Returns whether the given class would introduce any unwanted fields * in the target class. */ private boolean introducesUnwantedFields(ProgramClass programClass, ProgramClass targetClass) { // The class must not have any fields, or it must not be instantiated, // without any other subclasses. return programClass.u2fieldsCount != 0 && (InstantiationClassMarker.isInstantiated(targetClass) || (targetClass.subClasses != null && !isOnlySubClass(programClass, targetClass))); } /** * Returns whether the two given classes have initializers with the same * descriptors. */ private boolean haveAnyIdenticalInitializers(Clazz clazz, Clazz targetClass) { MemberCounter counter = new MemberCounter(); // TODO: Currently checking shared methods, not just initializers. // TODO: Allow identical methods. // Visit all methods, counting the ones that are also present in the // target class. clazz.methodsAccept(//new MemberNameFilter(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT), new SimilarMemberVisitor(targetClass, true, false, false, false, new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_ABSTRACT, counter))); return counter.getCount() > 0; } /** * Returns whether the given class would introduce any abstract methods * in the target class. */ private boolean introducesUnwantedAbstractMethods(Clazz clazz, ProgramClass targetClass) { // It's ok if the target class is already abstract and it has at most // the class as a subclass. if ((targetClass.getAccessFlags() & (ClassConstants.INTERNAL_ACC_ABSTRACT | ClassConstants.INTERNAL_ACC_INTERFACE)) != 0 && (targetClass.subClasses == null || isOnlySubClass(clazz, targetClass))) { return false; } MemberCounter counter = new MemberCounter(); Set targetSet = new HashSet(); // Collect all abstract methods, and similar abstract methods in the // class hierarchy of the target class. clazz.methodsAccept(new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0, new MultiMemberVisitor(new MemberVisitor[] { counter, new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0, new MemberCollector(targetSet))) }))); return targetSet.size() < counter.getCount(); } /** * Returns whether the given class overrides any methods in the given * target class. */ private boolean overridesAnyMethods(Clazz clazz, Clazz targetClass) { MemberCounter counter = new MemberCounter(); // Visit all non-private non-static methods, counting the ones that are // being overridden in the class hierarchy of the target class. clazz.methodsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT, new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)), new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)), new SimilarMemberVisitor(targetClass, true, true, false, false, new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT, counter)))))); return counter.getCount() > 0; } /** * Returns whether the given class or its subclasses shadow any methods in * the given target class. */ private boolean shadowsAnyMethods(Clazz clazz, Clazz targetClass) { MemberCounter counter = new MemberCounter(); // Visit all private methods, counting the ones that are shadowing // non-private methods in the class hierarchy of the target class. clazz.hierarchyAccept(true, false, false, true, new AllMethodVisitor( new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0, new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)), new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, counter)))))); // Visit all static methods, counting the ones that are shadowing // non-private methods in the class hierarchy of the target class. clazz.hierarchyAccept(true, false, false, true, new AllMethodVisitor( new MemberAccessFilter(ClassConstants.INTERNAL_ACC_STATIC, 0, new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)), new SimilarMemberVisitor(targetClass, true, true, true, false, new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, counter)))))); return counter.getCount() > 0; } public static void setTargetClass(Clazz clazz, Clazz targetClass) { ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz); if (info != null) { info.setTargetClass(targetClass); } } public static Clazz getTargetClass(Clazz clazz) { Clazz targetClass = null; // Return the last target class, if any. while (true) { ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz); if (info == null) { return targetClass; } clazz = info.getTargetClass(); if (clazz == null) { return targetClass; } targetClass = clazz; } } }