/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2010, Stefan Hepp (stefan@stefant.org). * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common.tools; import com.jopdesign.common.AppInfo; import com.jopdesign.common.ClassInfo; import com.jopdesign.common.FieldInfo; import com.jopdesign.common.KeyManager; import com.jopdesign.common.MemberInfo; import com.jopdesign.common.MethodCode; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.code.InvokeSite; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.type.ArrayTypeInfo; import com.jopdesign.common.type.FieldRef; import com.jopdesign.common.type.MemberID; import com.jopdesign.common.type.MethodRef; import com.jopdesign.common.type.ObjectTypeInfo; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InvokeInstruction; import org.apache.log4j.Logger; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * This class can be used to mark all used class members, to load missing classes (following only used code) * and to remove unused classes and class members. * * TODO If option is enabled: Mark methods visited by constantpool reference as 'grey' and do not follow, * if a gray method is visited by findImplementations, follow and mark * In a second pass, visit all gray methods, try to make abstract, or if making abstract is not possible, * mark used and visit * Then remove all unused * * @author Stefan Hepp (stefan@stefant.org) */ public class UsedCodeFinder { private static final Logger logger = Logger.getLogger(LogConfig.LOG_STRUCT + ".UsedCodeFinder"); private static KeyManager.CustomKey keyUsed; /** * Marker used by this class: UNUSED if a member is not marked, * MARKED if a member is referred to by the code but it has not been visited recursively, * USED if a member is used and has been visited recursively. */ public enum Mark { UNUSED, MARKED, USED } private final AppInfo appInfo; private Set<String> ignoredClasses; private static KeyManager.CustomKey getUseMarker() { if (keyUsed == null) { keyUsed = KeyManager.getSingleton().registerKey(KeyManager.KeyType.STRUCT, "UsedCodeFinder"); } return keyUsed; } /** * Create a new code finder */ public UsedCodeFinder() { this.appInfo = AppInfo.getSingleton(); this.ignoredClasses = new HashSet<String>(1); } /** * @return true if we found some referenced classes which were excluded. */ public boolean hasIgnoredClasses() { return !ignoredClasses.isEmpty(); } /** * Reset all loaded classes and their members to 'unused'. */ public void resetMarks() { KeyManager km = KeyManager.getSingleton(); km.clearAllValues(getUseMarker()); ignoredClasses.clear(); } /** * Check if a MemberInfo (class, method, field) has been marked as used. * If a member has not been marked, we assume it is unused. * * @param member the member to check. * @return the marker value. */ public Mark getMark(MemberInfo member) { Mark mark = (Mark) member.getCustomValue(getUseMarker()); // if it hasn't been marked, assume unused return mark != null ? mark : Mark.UNUSED; } /** * Mark a member as used. Note that this does not recurse down or marks the container as used. * * @see #markUsedMembers(ClassInfo,boolean) * @see #markUsedMembers(MethodInfo) * @see #markUsedMembers(FieldInfo) * @param member the member to mark as used. * @param markUsed if true, mark as USED, not as MARKED. * @return the old marker value. */ public Mark setMark(MemberInfo member, boolean markUsed) { if (!markUsed) { // If the member is already marked used, do not mark it as 'not visited' if (getMark(member) == Mark.USED) return Mark.USED; } Mark mark = (Mark) member.setCustomValue(getUseMarker(), markUsed ? Mark.USED : Mark.MARKED); return mark != null ? mark : Mark.UNUSED; } /** * Mark all used members, starting at all AppInfo roots. * <p> * To avoid marking unused Runnable.run methods, remove them from the callgraph roots first. * </p> * * @see #markUsedMembers(ClassInfo,boolean) * @see #markUsedMembers(MethodInfo) * @see #markUsedMembers(FieldInfo) */ public void markUsedMembers() { resetMarks(); // This contains all application and JVM root methods and -classes for (MethodInfo root : appInfo.getRootMethods()) { // this also marks the containing class as used, and includes the main method markUsedMembers(root); } // We do not need to visit all <clinit>, they are marked when the classes are visited // but we need to add all Runnable.run() methods as root methods for (MethodInfo run : appInfo.getThreadRootMethods(true)) { markUsedMembers(run); } if (ignoredClasses.size() > 0 ) { int num = ignoredClasses.size(); logger.info("Ignored " + num + " referenced "+ (num == 1 ? "class: " : "classes: ") + ignoredClasses); } } public void markUsedMembers(ClassInfo rootClass, boolean visitMembers) { // has already been visited before, do not recurse down again. if (!visitMembers && setMark(rootClass,true)==Mark.USED) return; // visit superclass and interfaces, attributes, but not methods or fields if (logger.isTraceEnabled()) { logger.trace("Visiting references of "+rootClass); } Set<String> found = ConstantPoolReferenceFinder.findReferencedMembers(rootClass, false); visitReferences(found); if (visitMembers) { for (FieldInfo field : rootClass.getFields()) { markUsedMembers(field); } for (MethodInfo method : rootClass.getMethods()) { markUsedMembers(method); } } else { // at least we need to visit the static initializer MethodInfo clinit = rootClass.getMethodInfo(ClinitOrder.clinitSig); if (clinit != null) { markUsedMembers(clinit); } // TODO if this implements Runnable, we could also mark the run() method, but using the callgraph // to find used threads is a bit more flexible } } public void markUsedMembers(FieldInfo rootField) { // has already been visited before, do not recurse down again. if (setMark(rootField,true)==Mark.USED) return; // mark the class containing this method, so we do not need to worry about // marking the class when marking root fields. markUsedMembers(rootField.getClassInfo(), false); // visit type info, attributes, constantValue if (logger.isTraceEnabled()) { logger.trace("Visiting references of "+rootField); } Set<String> found = ConstantPoolReferenceFinder.findReferencedMembers(rootField); visitReferences(found); } public void markUsedMembers(MethodInfo rootMethod) { // has already been visited before, do not recurse down again. if (setMark(rootMethod,true)==Mark.USED) return; // mark the class containing this method, so we do not need to worry about // marking the class when marking root methods. markUsedMembers(rootMethod.getClassInfo(), false); // visit parameters, attributes, instructions, tables, .. if (logger.isTraceEnabled()) { logger.trace("Visiting references of "+rootMethod); } Set<String> found = ConstantPoolReferenceFinder.findReferencedMembers(rootMethod); visitReferences(found); if (rootMethod.hasCode()) { visitInvokeSites(rootMethod.getCode()); } } private void visitReferences(Set<String> refs) { for (String id : refs) { // The member IDs returned by the reference finder use a syntax which is // always unique, so if in doubt, we need to interpret it as a classname, not a fieldname MemberID sig = MemberID.parse(id, false); // find/load the corresponding classInfo ClassInfo cls = getClassInfo(sig); // class has been excluded from loading, skip this class if (cls == null) { continue; } // referenced class is used, visit it if it has not yet been visited, but skip its class members // Note that this class might be different than the class containing the class member markUsedMembers(cls,false); // check if this id specifies a class member (or just a class, in this case we are done) if (sig.hasMethodSignature()) { // It's a method! mark the method as used (implementations are marked later) MethodRef ref = appInfo.getMethodRef(sig); MethodInfo method = ref.getMethodInfo(); // We need not go down recursively here, since all possible implementations will be marked later, // and we might find some unused code. But we may want to keep the referenced method so that // we keep the declarations. We mark it without following it and make it abstract later. if (method != null) { setMark(method, false); } } else if (sig.hasMemberName()) { // It's a field! No need to look in subclasses, fields are not virtual FieldRef ref = appInfo.getFieldRef(sig); FieldInfo field = ref.getFieldInfo(); if (field != null) { markUsedMembers(field); } } } } private void visitInvokeSites(MethodCode code) { for (InvokeSite invokeSite : code.getInvokeSites()) { // find all implementations for each invoke, mark them as used. for (MethodInfo method : findMethods(invokeSite)) { markUsedMembers(method); } } } private void ignoreClass(String className) { logger.debug("Ignored referenced class " +className); ignoredClasses.add(className); } private ClassInfo getClassInfo(MemberID sig) { String className; if (sig.isArrayClass()) { ArrayTypeInfo at = ArrayTypeInfo.parse(sig.getClassName()); if (at.getElementType() instanceof ObjectTypeInfo) { className = ((ObjectTypeInfo)at.getElementType()).getClassRef().getClassName(); } else { return null; } } else { className = sig.getClassName(); } ClassInfo classInfo = appInfo.getClassInfo(className); if (classInfo == null) { ignoreClass(className); } return classInfo; } private Collection<MethodInfo> findMethods(InvokeSite invoke) { // this checks the callgraph, if available, else the type graph // We do not need to use callstrings here: If a method can be reached over any callstring // we need to keep it anyway. // We could load classes on the fly here instead of checking the callgraph, basically // - use loadClass in getClassInfo() // - load and visit all superclasses, mark implementation in superclass as used if inherited // - visit all known subclasses, mark this method as used // - when a class is loaded, visit all methods which are used in the superclasses return appInfo.findImplementations(invoke); } }