All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.optimize.peephole.ClassMerger Maven / Gradle / Ivy

Go to download

ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode

There is a newer version: 7.6.0
Show newest version
/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2021 Guardsquare NV
 *
 * 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import proguard.classfile.*;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.visitor.*;
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.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;

import static proguard.classfile.attribute.Attribute.NEST_HOST;
import static proguard.classfile.attribute.Attribute.NEST_MEMBERS;

/**
 * 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
implements   ClassVisitor,
             ConstantVisitor
{
    private static final Logger logger = LogManager.getLogger(ClassMerger.class);
    //*
    private static final boolean DEBUG   = false;
    private static final boolean DETAILS = false;
    /*/
    private static       boolean DEBUG   = System.getProperty("cm") != null;
    private static       boolean DETAILS = System.getProperty("cmd") != null;
    //*/


    private final ProgramClass targetClass;
    private final boolean      allowAccessModification;
    private final boolean      mergeInterfacesAggressively;
    private final boolean      mergeWrapperClasses;
    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,
                       boolean      mergeWrapperClasses)
    {
        this(targetClass,
             allowAccessModification,
             mergeInterfacesAggressively,
             mergeWrapperClasses,
             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,
                       boolean      mergeWrapperClasses,
                       ClassVisitor extraClassVisitor)
    {
        this.targetClass                 = targetClass;
        this.allowAccessModification     = allowAccessModification;
        this.mergeInterfacesAggressively = mergeInterfacesAggressively;
        this.mergeWrapperClasses         = mergeWrapperClasses;
        this.extraClassVisitor           = extraClassVisitor;
    }


    // Implementations for ClassVisitor.

    @Override
    public void visitAnyClass(Clazz clazz) { }


    @Override
    public void visitProgramClass(ProgramClass programClass)
    {
        // Only merge program classes.

        //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)
        {
            logger.error("Unexpected error while merging classes:");
            logger.error("  Class        = [{}]", programClass.getName());
            logger.error("  Target class = [{}]", targetClass.getName());
            logger.error("  Exception    = [{}] ({})", ex.getClass().getName(), ex.getMessage());

            logger.debug("{}", () -> {
                StringWriter sw = new StringWriter();
                programClass.accept(new ClassPrinter(new PrintWriter(sw)));
                return sw.toString();
            });
            logger.debug("{}", () -> {
                StringWriter sw = new StringWriter();
                targetClass.accept(new ClassPrinter(new PrintWriter(sw)));
                return sw.toString();
            });
            throw ex;
        }
    }

    private void visitProgramClass0(ProgramClass programClass)
    {
        if (isMergeable(programClass))
        {
            // We're not actually merging the classes, but only copying the
            // contents from the source class to the target class. We'll
            // then let all other classes point to it. The shrinking step
            // will finally remove the source class.
            logger.debug("ClassMerger [{}] -> [{}]", programClass.getName(), targetClass.getName());
            logger.debug("  Source instantiated? [ {}]", InstantiationClassMarker.isInstantiated(programClass));
            logger.debug("  Target instantiated? [ {}]", InstantiationClassMarker.isInstantiated(targetClass));
            logger.debug("  Source interface? [{}]", ((programClass.getAccessFlags() & AccessConstants.INTERFACE)!=0));
            logger.debug("  Target interface? [{}]", ((targetClass.getAccessFlags() & AccessConstants.INTERFACE)!=0));
            logger.debug("  Source subclasses ({})", programClass.subClassCount);
            logger.debug("  Target subclasses ({})", targetClass.subClassCount);
            logger.debug("  Source superclass [{}]", programClass.getSuperClass().getName());
            logger.debug("  Target superclass [{}]", targetClass.getSuperClass().getName());

            logger.trace("=== Before ===");
            logger.trace("{}", () -> {
                StringWriter sw = new StringWriter();
                programClass.accept(new ClassPrinter(new PrintWriter(sw)));
                return sw.toString();
            });
            logger.trace("{}", () -> {
                StringWriter sw = new StringWriter();
                targetClass.accept(new ClassPrinter(new PrintWriter(sw)));
                return sw.toString();
            });

            // Combine the access flags.
            int targetAccessFlags = targetClass.getAccessFlags();
            int sourceAccessFlags = programClass.getAccessFlags();

            targetClass.u2accessFlags =
                ((targetAccessFlags &
                  sourceAccessFlags) &
                 (AccessConstants.INTERFACE |
                  AccessConstants.ABSTRACT)) |
                ((targetAccessFlags |
                  sourceAccessFlags) &
                 (AccessConstants.PUBLIC      |
                  AccessConstants.SUPER       |
                  AccessConstants.ANNOTATION  |
                  AccessConstants.ENUM));

            // Copy over the superclass, if it's a non-interface class being
            // merged into an interface class.
            // However, we're currently never merging in a way that changes the
            // superclass.
            //if ((programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 &&
            //    (targetClass.getAccessFlags()  & AccessConstants.INTERFACE) != 0)
            //{
            //    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.
            // Note that the code shouldn't be iterating over the original
            // list at this point.
            programClass.interfaceConstantsAccept(
                new ExceptClassConstantFilter(targetClass.getName(),
                new ImplementedClassConstantFilter(targetClass,
                new ImplementingClassConstantFilter(targetClass,
                new InterfaceAdder(targetClass)))));

            // Make sure that the interfaces of the target class have the
            // target class as subclass. We'll have to clean up the
            // subclasses further when we actually apply the targets.
            targetClass.interfaceConstantsAccept(
                new ReferencedClassVisitor(
                new SubclassFilter(targetClass,
                new SubclassAdder(targetClass))));

            // Create a visitor to copy class members.
            MemberVisitor memberAdder =
                new MemberAdder(targetClass,
                new MultiMemberVisitor(
                    // Copy or link optimization info.
                    new MyMemberOptimizationInfoCopier(),

                    // Mark copied members as being modified.
                    new ProcessingFlagSetter(ProcessingFlags.MODIFIED)
                 ));

            // Copy over the fields (only static from wrapper classes).
            programClass.fieldsAccept(mergeWrapperClasses ?
                new MemberAccessFilter( AccessConstants.STATIC, 0, memberAdder) :
                memberAdder);

            // Copy over the methods (not initializers from wrapper classes).
            programClass.methodsAccept(mergeWrapperClasses ?
                new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.METHOD_NAME_INIT)), memberAdder) :
                memberAdder);

            // Copy over the other attributes.
            programClass.attributesAccept(
                new AttributeNameFilter(new NotMatcher(
                    new OrMatcher(new FixedStringMatcher(Attribute.BOOTSTRAP_METHODS),
                    new OrMatcher(new FixedStringMatcher(Attribute.SOURCE_FILE),
                    new OrMatcher(new FixedStringMatcher(Attribute.INNER_CLASSES),
                                  new FixedStringMatcher(Attribute.ENCLOSING_METHOD))))),
                new AttributeAdder(targetClass, true)));

            // Update the optimization information of the target class.
            logger.debug("merging optimization info for {} and {}", targetClass.getName(), programClass.getName());

            ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(targetClass)
                .merge(ClassOptimizationInfo.getClassOptimizationInfo(programClass));

            // Remember to replace the inlined class by the target class.
            setTargetClass(programClass, targetClass);

            logger.trace("=== After ====");
            logger.trace("Target instantiated? {}", InstantiationClassMarker.isInstantiated(targetClass));
            logger.trace("{}", () -> {
                StringWriter sw = new StringWriter();
                targetClass.accept(new ClassPrinter(new PrintWriter(sw)));
                return sw.toString();
            });
            logger.trace("  Interfaces:");
            logger.trace("{}", () -> {
                StringWriter sw = new StringWriter();
                targetClass.interfaceConstantsAccept(new ClassPrinter(new PrintWriter(sw)));
                return sw.toString();
            });

            // Visit the merged class, if required.
            if (extraClassVisitor != null)
            {
                extraClassVisitor.visitProgramClass(programClass);
            }
        }
    }


    public boolean isMergeable(ProgramClass programClass)
    {
        return !programClass.equals(targetClass) &&

               (!DETAILS || print(programClass, "isKept?")) &&

               // Don't merge classes that must be preserved.
               !KeepMarker.isKept(programClass) &&
               !KeepMarker.isKept(targetClass) &&

               (!DETAILS || print(programClass, "already retargeted?")) &&

               // Only merge classes that haven't been retargeted yet.
               getTargetClass(programClass) == null &&
               getTargetClass(targetClass) == null &&

               (!DETAILS || print(programClass, "isAnnotation?")) &&

               // Don't merge annotation classes, with all their reflection and
               // infinite recursion.
               (programClass.getAccessFlags() & AccessConstants.ANNOTATION) == 0 &&

               (!DETAILS || print(programClass, "Version?")) &&

               // Only merge classes with equal class versions.
               programClass.u4version == targetClass.u4version &&

               (!DETAILS || print(programClass, "Nest host or member?")) &&

               // Don't merge nest hosts or members.
               !isNestHostOrMember(programClass) &&

               (!DETAILS || print(programClass, "No non-copiable attributes?")) &&

               // The class to be merged into the target class must not have
               // non-copiable attributes (InnerClass, EnclosingMethod),
               // unless it is a synthetic class.
               (mergeWrapperClasses ||
                (programClass.getAccessFlags() & AccessConstants.SYNTHETIC) != 0 ||
                !hasNonCopiableAttributes(programClass)) &&

               (!DETAILS || print(programClass, "Package visibility?")) &&

               // Only merge classes if we can change the access permissions, 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() &
                  AccessConstants.PUBLIC) != 0 &&
                 !PackageVisibleMemberContainingClassMarker.containsPackageVisibleMembers(programClass) &&
                 !PackageVisibleMemberInvokingClassMarker.invokesPackageVisibleMembers(programClass)) ||
                ClassUtil.internalPackageName(programClass.getName()).equals(
                    ClassUtil.internalPackageName(targetClass.getName()))) &&

               (!DETAILS || print(programClass, "Interface/abstract/single?")) &&

               // Only merge two classes or two interfaces or two abstract classes,
               // or a single implementation into its interface.
               ((programClass.getAccessFlags() &
                 (AccessConstants.INTERFACE |
                  AccessConstants.ABSTRACT)) ==
                (targetClass.getAccessFlags() &
                 (AccessConstants.INTERFACE |
                  AccessConstants.ABSTRACT)) ||
                (isOnlySubClass(programClass, targetClass) &&
                 programClass.getSuperClass() != null &&
                 (programClass.getSuperClass().equals(targetClass) ||
                  programClass.getSuperClass().equals(targetClass.getSuperClass())))) &&

               (!DETAILS || print(programClass, "Indirect implementation?")) &&

               // One class must not implement the other class indirectly.
               !indirectlyImplementedInterfaces(programClass).contains(targetClass) &&
               !targetClass.extendsOrImplements(programClass) &&

               (!DETAILS || print(programClass, "Interfaces same subinterfaces?")) &&

               // Interfaces must have exactly the same subinterfaces, not
               // counting themselves, to avoid any loops in the interface
               // hierarchy.
               ((programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 ||
                (targetClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 ||
                subInterfaces(programClass, targetClass).equals(subInterfaces(targetClass, programClass))) &&

               (!DETAILS || print(programClass, "Same initialized superclasses?")) &&

               // The two classes must have the same superclasses and interfaces
               // with static initializers.
               sideEffectSuperClasses(programClass).equals(sideEffectSuperClasses(targetClass)) &&

               (!DETAILS || print(programClass, "Same instanceofed superclasses?")) &&

               // The two classes must have the same superclasses and interfaces
               // that are tested with 'instanceof'.
               instanceofedSuperClasses(programClass).equals(instanceofedSuperClasses(targetClass)) &&

               (!DETAILS || print(programClass, "Same caught superclasses?")) &&

               // The two classes must have the same superclasses that are caught
               // as exceptions.
               caughtSuperClasses(programClass).equals(caughtSuperClasses(targetClass)) &&

               (!DETAILS || print(programClass, "Not .classed?")) &&

               // The two classes must not both be part of a .class construct.
               !(DotClassMarker.isDotClassed(programClass) &&
                 DotClassMarker.isDotClassed(targetClass)) &&

               (!DETAILS || print(programClass, "No clashing fields?")) &&

               // The classes must not have clashing fields.
               (mergeWrapperClasses ||
                !haveAnyIdenticalFields(programClass, targetClass)) &&

               (!DETAILS || print(programClass, "No unwanted fields?")) &&

               // The two classes must not introduce any unwanted fields.
               (mergeWrapperClasses ||
                !introducesUnwantedFields(programClass, targetClass) &&
                !introducesUnwantedFields(targetClass, programClass)) &&

               (!DETAILS || print(programClass, "No shadowed fields?")) &&

               // The two classes must not shadow each others fields.
               (mergeWrapperClasses ||
                !shadowsAnyFields(programClass, targetClass) &&
                !shadowsAnyFields(targetClass, programClass)) &&

               (!DETAILS || print(programClass, "No clashing methods?")) &&

               // The classes must not have clashing methods.
               !haveAnyIdenticalMethods(programClass, targetClass) &&

               (!DETAILS || print(programClass, "No abstract methods?")) &&

               // The classes must not introduce abstract methods, unless
               // explicitly allowed.
               (mergeInterfacesAggressively ||
                (!introducesUnwantedAbstractMethods(programClass, targetClass) &&
                 !introducesUnwantedAbstractMethods(targetClass, programClass))) &&

               (!DETAILS || print(programClass, "No native methods?")) &&
               // No native methods in the program class that is getting inlined.
               noNativeMethodIn(programClass) &&

               (!DETAILS || print(programClass, "No overridden methods?")) &&

               // The classes must not override each others concrete methods.
               !overridesAnyMethods(programClass, targetClass) &&
               !overridesAnyMethods(targetClass, programClass) &&

               (!DETAILS || print(programClass, "No shadowed methods?")) &&

               // The classes must not shadow each others non-private methods.
               !shadowsAnyMethods(programClass, targetClass) &&
               !shadowsAnyMethods(targetClass, programClass) &&

               (!DETAILS || print(programClass, "No type variables/parameterized types?")) &&

               // The two classes must not have a signature attribute as type variables
               // and/or parameterized types can not always be merged.
               !hasSignatureAttribute(programClass) &&
               !hasSignatureAttribute(targetClass);
    }

    private boolean noNativeMethodIn(ProgramClass programClass)
    {
        MemberCounter counter = new MemberCounter();
        programClass.methodsAccept(new MemberAccessFilter(AccessConstants.NATIVE, 0, counter));
        return counter.getCount() == 0;
    }

    private boolean print(ProgramClass programClass, String message)
    {
        logger.debug("Merge [{}] <- [{}] {}", targetClass.getName(), programClass.getName(), message);

        return true;
    }


    // Small utility methods.

    /**
     * Returns whether a given class is the only subclass of another given class.
     */
    private boolean isOnlySubClass(Clazz        subClass,
                                   ProgramClass clazz)
    {
        return clazz.subClassCount == 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 interface subclasses, not including the given class.
     */
    private Set subInterfaces(Clazz clazz, Clazz exceptClass)
    {
        Set set = new HashSet();

        // Visit all subclasses, collecting the interface classes.
        clazz.hierarchyAccept(false, false, false, true,
            new ClassAccessFilter(AccessConstants.INTERFACE, 0,
            new ExceptClassesFilter(new Clazz[] { exceptClass },
            new ClassCollector(set))));

        return set;
    }


    /**
     * Returns the set of superclasses and interfaces that are initialized.
     */
    private Set sideEffectSuperClasses(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 SideEffectClassFilter(
                              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)
    {
        // Don't bother if this isn't an exception at all.
        if (!clazz.extends_(ClassConstants.NAME_JAVA_LANG_THROWABLE))
        {
            return Collections.EMPTY_SET;
        }

        // Visit all superclasses, collecting the ones that are caught
        // (plus java.lang.Object, in the current implementation).
        Set set = new HashSet();

        clazz.hierarchyAccept(true, true, false, false,
                              new CaughtClassFilter(
                              new ClassCollector(set)));

        return set;
    }


    /**
     * Returns whether the given class has a Signature attributes containing
     * type variables or parameterized types.
     */
    static boolean hasSignatureAttribute(Clazz clazz)
    {
        AttributeCounter counter = new AttributeCounter();

        clazz.attributesAccept(
            new AttributeNameFilter(
            new FixedStringMatcher(Attribute.SIGNATURE),
                counter));

        return counter.getCount() > 0;
    }

    /**
     * Returns whether the two given classes have fields with the same
     * names and descriptors.
     */
    private boolean haveAnyIdenticalFields(Clazz clazz,
                                           Clazz targetClass)
    {
        MemberCounter counter = new MemberCounter();

        // Visit all fields, counting the with the same name and descriptor in
        // the target class.
        clazz.fieldsAccept(new SimilarMemberVisitor(targetClass, true, false, false, false,
                           counter));

        return counter.getCount() > 0;
    }


    /**
     * Returns whether the given class would introduce any unwanted fields
     * in the target class.
     */
    private boolean introducesUnwantedFields(Clazz        programClass,
                                             ProgramClass targetClass)
    {
        // It's ok if the target class is never instantiated and does not
        // have any subclasses except for maybe the source class.
        if (!InstantiationClassMarker.isInstantiated(targetClass) &&
            (targetClass.subClassCount == 0 ||
             isOnlySubClass(programClass, targetClass)))
        {
            return false;
        }

        MemberCounter counter = new MemberCounter();

        // Count all non-static fields in the the source class.
        programClass.fieldsAccept(new MemberAccessFilter(0, AccessConstants.STATIC,
                                  counter));

        return counter.getCount() > 0;
    }


    /**
     * Returns whether the given class or its subclasses shadow any fields in
     * the given target class.
     */
    private boolean shadowsAnyFields(Clazz clazz,
                                     Clazz targetClass)
    {
        MemberCounter counter = new MemberCounter();

        // Visit all fields, counting the ones that are shadowing non-private
        // fields in the class hierarchy of the target class.
        clazz.hierarchyAccept(true, false, false, true,
                              new AllFieldVisitor(
                              new SimilarMemberVisitor(targetClass, true, true, true, false,
                              new MemberAccessFilter(0, AccessConstants.PRIVATE,
                              counter))));

        return counter.getCount() > 0;
    }


    /**
     * Returns whether the two given classes have class members with the same
     * name and descriptor.
     */
    private boolean haveAnyIdenticalMethods(Clazz clazz,
                                            Clazz targetClass)
    {
        MemberCounter counter = new MemberCounter();

        // Visit all non-abstract methods, counting the ones that are also
        // present in the target class.
        clazz.methodsAccept(new MemberAccessFilter(0, AccessConstants.ABSTRACT,
                            new SimilarMemberVisitor(targetClass, true, false, false, false,
                            new MemberAccessFilter(0, AccessConstants.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 does not
        // have any subclasses except for maybe the source class.
        if ((targetClass.getAccessFlags() &
             (AccessConstants.ABSTRACT |
              AccessConstants.INTERFACE)) != 0 &&
            (targetClass.subClassCount == 0 ||
             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(AccessConstants.ABSTRACT, 0,
                            new MultiMemberVisitor(
                                counter,

                                new SimilarMemberVisitor(targetClass, true, true, true, false,
                                new MemberAccessFilter(AccessConstants.ABSTRACT, 0,
                                new MemberCollector(false, true, true, targetSet)))
                            )));

        return targetSet.size() < counter.getCount();
    }


    /**
     * Returns whether the given class overrides any methods in the given
     * target class.
     */
    private boolean overridesAnyMethods(Clazz        clazz,
                                        ProgramClass targetClass)
    {
        // It's ok if the target class is never instantiated and does
        // not have any subclasses except for maybe the source class.
        if (!InstantiationClassMarker.isInstantiated(targetClass) &&
            (targetClass.subClassCount == 0 ||
             isOnlySubClass(clazz, targetClass)))
        {
            return false;
        }

        MemberCounter counter = new MemberCounter();

        // Visit all non-abstract methods, counting the ones that are
        // overriding methods in the class hierarchy of the target class.
        clazz.methodsAccept(new MemberAccessFilter(0, AccessConstants.ABSTRACT,
                            new InitializerMethodFilter(null,
                            new SimilarMemberVisitor(targetClass, true, true, false, false,
                            new MemberAccessFilter(0, AccessConstants.PRIVATE | AccessConstants.STATIC | AccessConstants.ABSTRACT,
                            counter)))));

        return counter.getCount() > 0;
    }


    /**
     * Returns whether the given class or its subclasses have private or
     * static methods that shadow any methods in the given target class.
     */
    private boolean shadowsAnyMethods(Clazz clazz,
                                      Clazz targetClass)
    {
        // It's ok if the source class already extends the target class
        // or (in practice) vice versa.
        if (clazz.extends_(targetClass) ||
            targetClass.extends_(clazz))
        {
            return false;
        }

        MemberCounter counter = new MemberCounter();

        // Visit all methods, counting the ones that are shadowing
        // final methods in the class hierarchy of the target class.
        clazz.hierarchyAccept(true, false, false, true,
                              new AllMethodVisitor(
                              new InitializerMethodFilter(null,
                              new SimilarMemberVisitor(targetClass, true, true, false, false,
                              new MemberAccessFilter(AccessConstants.FINAL, 0,
                                                     counter)))));
        if (counter.getCount() > 0)
        {
            return true;
        }

        // 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(AccessConstants.PRIVATE, 0,
                              new InitializerMethodFilter(null,
                              new SimilarMemberVisitor(targetClass, true, true, true, false,
                              new MemberAccessFilter(0, AccessConstants.PRIVATE,
                                                     counter))))));
        if (counter.getCount() > 0)
        {
            return true;
        }

        // 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(AccessConstants.STATIC, 0,
                              new InitializerMethodFilter(null,
                              new SimilarMemberVisitor(targetClass, true, true, true, false,
                              new MemberAccessFilter(0, AccessConstants.PRIVATE,
                              counter))))));

        return counter.getCount() > 0;
    }


    /**
     * Returns whether the given class has any attributes that can not be copied when
     * merging it into another class.
     */
    private boolean hasNonCopiableAttributes(Clazz clazz)
    {
        AttributeCounter counter = new AttributeCounter();

        // Copy over the other attributes.
        clazz.attributesAccept(
            new AttributeNameFilter(
                new OrMatcher(new FixedStringMatcher(Attribute.INNER_CLASSES),
                              new FixedStringMatcher(Attribute.ENCLOSING_METHOD)),
            counter));

        return counter.getCount() > 0;
    }


    public static boolean isNestHostOrMember(Clazz clazz)
    {
        AttributeCounter counter = new AttributeCounter();

        clazz.attributesAccept(
            new AttributeNameFilter(
                new OrMatcher(new FixedStringMatcher(Attribute.NEST_HOST),
                              new FixedStringMatcher(Attribute.NEST_MEMBERS)),
            counter));

        return counter.getCount() > 0;
    }

    public static void setTargetClass(Clazz clazz, Clazz targetClass)
    {
        ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(clazz).setTargetClass(targetClass);
    }


    public static Clazz getTargetClass(Clazz clazz)
    {
        Clazz targetClass = null;

        // Return the last target class, if any.
        while (true)
        {
            clazz = ClassOptimizationInfo.getClassOptimizationInfo(clazz).getTargetClass();
            if (clazz == null)
            {
                return targetClass;
            }

            targetClass = clazz;
        }
    }


    /**
     * This MemberVisitor copies field optimization info from copied fields.
     */
    private static class MyMemberOptimizationInfoCopier
    implements           MemberVisitor
    {
        public void visitProgramField(ProgramClass programClass, ProgramField programField)
        {
            // Copy the optimization info from the field that was just copied.
            ProgramField copiedField = (ProgramField)programField.getProcessingInfo();
            Object       info        = copiedField.getProcessingInfo();

            programField.setProcessingInfo(info instanceof ProgramFieldOptimizationInfo ?
                new ProgramFieldOptimizationInfo((ProgramFieldOptimizationInfo)info) :
                info);
        }


        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
        {
            // Linked methods share their optimization info.
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy