summaryrefslogtreecommitdiffstats
path: root/src/proguard/optimize/peephole/ClassMerger.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/proguard/optimize/peephole/ClassMerger.java')
-rw-r--r--src/proguard/optimize/peephole/ClassMerger.java541
1 files changed, 541 insertions, 0 deletions
diff --git a/src/proguard/optimize/peephole/ClassMerger.java b/src/proguard/optimize/peephole/ClassMerger.java
new file mode 100644
index 0000000..1e1a950
--- /dev/null
+++ b/src/proguard/optimize/peephole/ClassMerger.java
@@ -0,0 +1,541 @@
+/*
+ * 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;
+ }
+ }
+} \ No newline at end of file