/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2003-2007 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba.ch; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Set; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.BasicType; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.ObjectTypeFactory; import edu.umd.cs.findbugs.ba.XClass; import edu.umd.cs.findbugs.ba.XMethod; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.internalAnnotations.DottedClassName; import edu.umd.cs.findbugs.util.DualKeyHashMap; import edu.umd.cs.findbugs.util.MapCache; /** * Class for performing class hierarchy queries. * Does <em>not</em> require JavaClass objects to be in memory. * Instead, uses XClass objects. * * @author David Hovemeyer */ @javax.annotation.ParametersAreNonnullByDefault public class Subtypes2 { public static final boolean ENABLE_SUBTYPES2 = true; public static final boolean ENABLE_SUBTYPES2_FOR_COMMON_SUPERCLASS_QUERIES = true; //SystemProperties.getBoolean("findbugs.subtypes2.superclass"); public static final boolean DEBUG = SystemProperties.getBoolean("findbugs.subtypes2.debug"); public static final boolean DEBUG_QUERIES = SystemProperties.getBoolean("findbugs.subtypes2.debugqueries"); private final InheritanceGraph graph; private final Map<ClassDescriptor, ClassVertex> classDescriptorToVertexMap; private final Map<ClassDescriptor, SupertypeQueryResults> supertypeSetMap; private final Map<ClassDescriptor, Set<ClassDescriptor>> subtypeSetMap; private final Set<XClass> xclassSet; private final DualKeyHashMap<ReferenceType, ReferenceType, ReferenceType> firstCommonSuperclassQueryCache; private final ObjectType SERIALIZABLE; private final ObjectType CLONEABLE; /** * Object to record the results of a supertype search. */ private static class SupertypeQueryResults { private Set<ClassDescriptor> supertypeSet = new HashSet<ClassDescriptor>(); private boolean encounteredMissingClasses = false; public void addSupertype(ClassDescriptor classDescriptor) { supertypeSet.add(classDescriptor); } public void setEncounteredMissingClasses(boolean encounteredMissingClasses) { this.encounteredMissingClasses = encounteredMissingClasses; } public boolean containsType(ClassDescriptor possibleSupertypeClassDescriptor) throws ClassNotFoundException { if (supertypeSet.contains(possibleSupertypeClassDescriptor)) { return true; } else if (!encounteredMissingClasses) { return false; } else { // We don't really know which class was missing. // However, any missing classes will already have been reported. throw new ClassNotFoundException(); } } } /** * Constructor. */ public Subtypes2() { this.graph = new InheritanceGraph(); this.classDescriptorToVertexMap = new HashMap<ClassDescriptor, ClassVertex>(); this.supertypeSetMap = new MapCache<ClassDescriptor, SupertypeQueryResults>(500); this.subtypeSetMap = new MapCache<ClassDescriptor, Set<ClassDescriptor>>(500); this.xclassSet = new HashSet<XClass>(); this.SERIALIZABLE = ObjectTypeFactory.getInstance("java.io.Serializable"); this.CLONEABLE = ObjectTypeFactory.getInstance("java.lang.Cloneable"); this.firstCommonSuperclassQueryCache = new DualKeyHashMap<ReferenceType, ReferenceType, ReferenceType>(); } /** * @return Returns the graph. */ public InheritanceGraph getGraph() { return graph; } public static boolean instanceOf(@DottedClassName String dottedSubtype, @DottedClassName String dottedSupertype) { Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2(); ClassDescriptor subDescriptor = DescriptorFactory.createClassDescriptorFromDottedClassName(dottedSubtype); ClassDescriptor superDescriptor = DescriptorFactory.createClassDescriptorFromDottedClassName(dottedSupertype); try { return subtypes2.isSubtype(subDescriptor, superDescriptor); } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); return false; } } public static boolean instanceOf(ClassDescriptor subDescriptor, @DottedClassName String dottedSupertype) { Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2(); ClassDescriptor superDescriptor = DescriptorFactory.createClassDescriptorFromDottedClassName(dottedSupertype); try { return subtypes2.isSubtype(subDescriptor, superDescriptor); } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); return false; } } public static boolean instanceOf(JavaClass subtype, @DottedClassName String dottedSupertype) { Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2(); ClassDescriptor subDescriptor = DescriptorFactory.createClassDescriptor(subtype); ClassDescriptor superDescriptor = DescriptorFactory.createClassDescriptorFromDottedClassName(dottedSupertype); try { return subtypes2.isSubtype(subDescriptor, superDescriptor); } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); return false; } } /** * Add an application class, and its transitive supertypes, to the inheritance graph. * * @param appXClass application XClass to add to the inheritance graph */ public void addApplicationClass(XClass appXClass) { for(XMethod m : appXClass.getXMethods()) { if (m.isStub()) return; } ClassVertex vertex = addClassAndGetClassVertex(appXClass); vertex.markAsApplicationClass(); } public boolean isApplicationClass(ClassDescriptor descriptor) { assert descriptor != null; try { return resolveClassVertex(descriptor).isApplicationClass(); } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); return false; } } /** * Add a class or interface, and its transitive supertypes, to the inheritance graph. * * @param xclass XClass to add to the inheritance graph */ public void addClass(XClass xclass) { addClassAndGetClassVertex(xclass); } /** * Add an XClass and all of its supertypes to * the InheritanceGraph. * * @param xclass an XClass * @return the ClassVertex representing the class in * the InheritanceGraph */ private ClassVertex addClassAndGetClassVertex(XClass xclass) { if (xclass == null) { throw new IllegalStateException(); } LinkedList<XClass> workList = new LinkedList<XClass>(); workList.add(xclass); while (!workList.isEmpty()) { XClass work = workList.removeFirst(); ClassVertex vertex = classDescriptorToVertexMap.get(work.getClassDescriptor()); if (vertex != null && vertex.isFinished()) { // This class has already been processed. continue; } if (vertex == null) { vertex = ClassVertex.createResolvedClassVertex(work.getClassDescriptor(), work); addVertexToGraph(work.getClassDescriptor(), vertex); } addSupertypeEdges(vertex, workList); vertex.setFinished(true); } return classDescriptorToVertexMap.get(xclass.getClassDescriptor()); } private void addVertexToGraph(ClassDescriptor classDescriptor, ClassVertex vertex) { assert classDescriptorToVertexMap.get(classDescriptor) == null; if (DEBUG) { System.out.println("Adding " + classDescriptor.toDottedClassName() + " to inheritance graph"); } graph.addVertex(vertex); classDescriptorToVertexMap.put(classDescriptor, vertex); if (vertex.isResolved()) { xclassSet.add(vertex.getXClass()); } if (vertex.isInterface()) { // There is no need to add additional worklist nodes because java/lang/Object has no supertypes. addInheritanceEdge(vertex, DescriptorFactory.instance().getClassDescriptor("java/lang/Object"), false, null); } } /** * Determine whether or not a given ReferenceType is a subtype of another. * Throws ClassNotFoundException if the question cannot be answered * definitively due to a missing class. * * @param type a ReferenceType * @param possibleSupertype another Reference type * @return true if <code>type</code> is a subtype of <code>possibleSupertype</code>, false if not * @throws ClassNotFoundException if a missing class prevents a definitive answer */ public boolean isSubtype(ReferenceType type, ReferenceType possibleSupertype) throws ClassNotFoundException { // Eliminate some easy cases if (type.equals(possibleSupertype)) { return true; } if (possibleSupertype.equals(ObjectType.OBJECT)) return true; if (type.equals(ObjectType.OBJECT)) return false; boolean typeIsObjectType = (type instanceof ObjectType); boolean possibleSupertypeIsObjectType = (possibleSupertype instanceof ObjectType); if (typeIsObjectType && possibleSupertypeIsObjectType) { // Both types are ordinary object (non-array) types. return isSubtype((ObjectType) type, (ObjectType) possibleSupertype); } boolean typeIsArrayType = (type instanceof ArrayType); boolean possibleSupertypeIsArrayType = (possibleSupertype instanceof ArrayType); if (typeIsArrayType) { // Check superclass/interfaces if (possibleSupertype.equals(SERIALIZABLE) || possibleSupertype.equals(CLONEABLE)) { return true; } // We checked all of the possible class/interface supertypes, // so if possibleSupertype is not an array type, // then we can definitively say no if (!possibleSupertypeIsArrayType) { return false; } // Check array/array subtype relationship ArrayType typeAsArrayType = (ArrayType) type; ArrayType possibleSupertypeAsArrayType = (ArrayType) possibleSupertype; // Must have same number of dimensions if (typeAsArrayType.getDimensions() < possibleSupertypeAsArrayType.getDimensions()) { return false; } Type possibleSupertypeBasicType = possibleSupertypeAsArrayType.getBasicType(); if (!(possibleSupertypeBasicType instanceof ObjectType)) { return false; } Type typeBasicType = typeAsArrayType.getBasicType(); // If dimensions differ, see if element types are compatible. if (typeAsArrayType.getDimensions() > possibleSupertypeAsArrayType.getDimensions()) { return isSubtype(new ArrayType(typeBasicType,typeAsArrayType.getDimensions() - possibleSupertypeAsArrayType.getDimensions() ), (ObjectType) possibleSupertypeBasicType); } // type's base type must be a subtype of possibleSupertype's base type. // Note that neither base type can be a non-ObjectType if we are to answer yes. if (!(typeBasicType instanceof ObjectType)) { return false; } return isSubtype((ObjectType) typeBasicType, (ObjectType) possibleSupertypeBasicType); } // OK, we've exhausted the possibilities now return false; } public boolean isSubtype(ClassDescriptor subDesc, ClassDescriptor superDesc) throws ClassNotFoundException { assert subDesc != null; assert superDesc != null; if (subDesc.equals(superDesc)) return true; try { SupertypeQueryResults supertypeQueryResults = getSupertypeQueryResults(subDesc); return supertypeQueryResults.containsType(superDesc); } catch (ClassNotFoundException e) { XClass xclass = AnalysisContext.currentXFactory().getXClass(subDesc); if (xclass != null && superDesc.equals(xclass.getSuperclassDescriptor())) return true; throw e; } } /** * Determine whether or not a given ObjectType is a subtype of another. * Throws ClassNotFoundException if the question cannot be answered * definitively due to a missing class. * * @param type a ReferenceType * @param possibleSupertype another Reference type * @return true if <code>type</code> is a subtype of <code>possibleSupertype</code>, false if not * @throws ClassNotFoundException if a missing class prevents a definitive answer */ public boolean isSubtype(ObjectType type, ObjectType possibleSupertype) throws ClassNotFoundException { if (DEBUG_QUERIES) { System.out.println("isSubtype: check " + type + " subtype of " + possibleSupertype); } if (type.equals(possibleSupertype)) { if (DEBUG_QUERIES) { System.out.println(" ==> yes, types are same"); } return true; } ClassDescriptor typeClassDescriptor = DescriptorFactory.getClassDescriptor(type); ClassDescriptor possibleSuperclassClassDescriptor = DescriptorFactory.getClassDescriptor(possibleSupertype); // In principle, we should be able to answer no if the ObjectType objects // are not equal and possibleSupertype is final. // However, internally FindBugs creates special "subtypes" of java.lang.String // (DynamicStringType, StaticStringType, etc.)---see FindRefComparison detector. // These will end up resolving to the same ClassVertex as java.lang.String, // which will Do The Right Thing. if (false) { ClassVertex possibleSuperclassClassVertex = resolveClassVertex(possibleSuperclassClassDescriptor); if (possibleSuperclassClassVertex.isResolved() && possibleSuperclassClassVertex.getXClass().isFinal()) { if (DEBUG_QUERIES) { System.out.println(" ==> no, " + possibleSuperclassClassDescriptor + " is final"); } return false; } } // Get the supertype query results SupertypeQueryResults supertypeQueryResults = getSupertypeQueryResults(typeClassDescriptor); if (DEBUG_QUERIES) { System.out.println(" Superclass set: " + supertypeQueryResults.supertypeSet); } boolean isSubtype = supertypeQueryResults.containsType(possibleSuperclassClassDescriptor); if (DEBUG_QUERIES) { if (isSubtype) { System.out.println(" ==> yes, " + possibleSuperclassClassDescriptor + " is in superclass set"); } else { System.out.println(" ==> no, " + possibleSuperclassClassDescriptor + " is not in superclass set"); } } return isSubtype; } /** * Get the first common superclass of the given reference types. * Note that an interface type is never returned unless <code>a</code> and <code>b</code> are the * same type. Otherwise, we try to return as accurate a type as possible. * This method is used as the meet operator in TypeDataflowAnalysis, * and is intended to follow (more or less) the JVM bytecode verifier * semantics. * * <p>This method should be used in preference to the getFirstCommonSuperclass() * method in {@link ReferenceType}.</p> * * @param a a ReferenceType * @param b another ReferenceType * @return the first common superclass of <code>a</code> and <code>b</code> * @throws ClassNotFoundException */ public ReferenceType getFirstCommonSuperclass(ReferenceType a, ReferenceType b) throws ClassNotFoundException { // Easy case: same types if (a.equals(b)) { return a; } ReferenceType answer = checkFirstCommonSuperclassQueryCache(a, b); if (answer == null) { answer = computeFirstCommonSuperclassOfReferenceTypes(a, b); putFirstCommonSuperclassQueryCache(a, b, answer); } return answer; } private ReferenceType computeFirstCommonSuperclassOfReferenceTypes(ReferenceType a, ReferenceType b) throws ClassNotFoundException { boolean aIsArrayType = (a instanceof ArrayType); boolean bIsArrayType = (b instanceof ArrayType); if (aIsArrayType && bIsArrayType) { // Merging array types - kind of a pain. ArrayType aArrType = (ArrayType) a; ArrayType bArrType = (ArrayType) b; if (aArrType.getDimensions() == bArrType.getDimensions()) { return computeFirstCommonSuperclassOfSameDimensionArrays(aArrType, bArrType); } else { return computeFirstCommonSuperclassOfDifferentDimensionArrays(aArrType, bArrType); } } if (aIsArrayType || bIsArrayType) { // One of a and b is an array type, but not both. // Common supertype is Object. return ObjectType.OBJECT; } // Neither a nor b is an array type. // Find first common supertypes of ObjectTypes. return getFirstCommonSuperclass((ObjectType) a, (ObjectType) b); } /** * Get first common supertype of arrays with the same number of dimensions. * * @param aArrType an ArrayType * @param bArrType another ArrayType with the same number of dimensions * @return first common supertype * @throws ClassNotFoundException */ private ReferenceType computeFirstCommonSuperclassOfSameDimensionArrays(ArrayType aArrType, ArrayType bArrType) throws ClassNotFoundException { assert aArrType.getDimensions() == bArrType.getDimensions(); Type aBaseType = aArrType.getBasicType(); Type bBaseType = bArrType.getBasicType(); boolean aBaseIsObjectType = (aBaseType instanceof ObjectType); boolean bBaseIsObjectType = (bBaseType instanceof ObjectType); if (!aBaseIsObjectType || !bBaseIsObjectType) { assert (aBaseType instanceof BasicType) || (bBaseType instanceof BasicType); if (aArrType.getDimensions() > 1) { // E.g.: first common supertype of int[][] and WHATEVER[][] is Object[] return new ArrayType(Type.OBJECT, aArrType.getDimensions() - 1); } else { assert aArrType.getDimensions() == 1; // E.g.: first common supertype type of int[] and WHATEVER[] is Object return Type.OBJECT; } } else { assert (aBaseType instanceof ObjectType); assert (bBaseType instanceof ObjectType); // Base types are both ObjectTypes, and number of dimensions is same. // We just need to find the first common supertype of base types // and return a new ArrayType using that base type. ObjectType firstCommonBaseType = getFirstCommonSuperclass((ObjectType) aBaseType, (ObjectType) bBaseType); return new ArrayType(firstCommonBaseType, aArrType.getDimensions()); } } /** * Get the first common superclass of * arrays with different numbers of dimensions. * * @param aArrType an ArrayType * @param bArrType another ArrayType * @return ReferenceType representing first common superclass */ private ReferenceType computeFirstCommonSuperclassOfDifferentDimensionArrays(ArrayType aArrType, ArrayType bArrType) { assert aArrType.getDimensions() != bArrType.getDimensions(); boolean aBaseTypeIsPrimitive = (aArrType.getBasicType() instanceof BasicType); boolean bBaseTypeIsPrimitive = (bArrType.getBasicType() instanceof BasicType); if (aBaseTypeIsPrimitive || bBaseTypeIsPrimitive) { int minDimensions, maxDimensions; if (aArrType.getDimensions() < bArrType.getDimensions()) { minDimensions = aArrType.getDimensions(); maxDimensions = bArrType.getDimensions(); } else { minDimensions = bArrType.getDimensions(); maxDimensions = aArrType.getDimensions(); } if (minDimensions == 1) { // One of the types was something like int[]. // The only possible common supertype is Object. return Type.OBJECT; } else { // Weird case: e.g., // - first common supertype of int[][] and char[][][] is Object[] // because f.c.s. of int[] and char[][] is Object // - first common supertype of int[][][] and char[][][][][] is Object[][] // because f.c.s. of int[] and char[][][] is Object return new ArrayType(Type.OBJECT, maxDimensions - minDimensions); } } else { // Both a and b have base types which are ObjectTypes. // Since the arrays have different numbers of dimensions, the // f.c.s. will have Object as its base type. // E.g., f.c.s. of Cat[] and Dog[][] is Object[] return new ArrayType(Type.OBJECT, Math.min(aArrType.getDimensions(), bArrType.getDimensions())); } } /** * Get the first common superclass of the given object types. * Note that an interface type is never returned unless <code>a</code> and <code>b</code> are the * same type. Otherwise, we try to return as accurate a type as possible. * This method is used as the meet operator in TypeDataflowAnalysis, * and is intended to follow (more or less) the JVM bytecode verifier * semantics. * * <p>This method should be used in preference to the getFirstCommonSuperclass() * method in {@link ReferenceType}.</p> * * @param a an ObjectType * @param b another ObjectType * @return the first common superclass of <code>a</code> and <code>b</code> * @throws ClassNotFoundException */ public ObjectType getFirstCommonSuperclass(ObjectType a, ObjectType b) throws ClassNotFoundException { // Easy case if (a.equals(b)) { return a; } ObjectType firstCommonSupertype = (ObjectType) checkFirstCommonSuperclassQueryCache(a, b); if (firstCommonSupertype == null) { firstCommonSupertype = computeFirstCommonSuperclassOfObjectTypes(a, b); firstCommonSuperclassQueryCache.put(a, b, firstCommonSupertype); } return firstCommonSupertype; } private ObjectType computeFirstCommonSuperclassOfObjectTypes(ObjectType a, ObjectType b) throws ClassNotFoundException { ObjectType firstCommonSupertype; ClassDescriptor aDesc = DescriptorFactory.getClassDescriptor(a); ClassDescriptor bDesc = DescriptorFactory.getClassDescriptor(b); ClassVertex aVertex = resolveClassVertex(aDesc); ClassVertex bVertex = resolveClassVertex(bDesc); Set<ClassDescriptor> aSuperTypes = computeKnownSupertypes(aDesc); Set<ClassDescriptor> bSuperTypes = computeKnownSupertypes(bDesc); if (bSuperTypes.contains(aDesc)) return a; if (aSuperTypes.contains(bDesc)) return b; ArrayList<ClassVertex> aSuperList = getAllSuperclassVertices(aVertex); ArrayList<ClassVertex> bSuperList = getAllSuperclassVertices(bVertex); // Work backwards until the lists diverge. // The last element common to both lists is the first // common superclass. int aIndex = aSuperList.size() - 1; int bIndex = bSuperList.size() - 1; ClassVertex lastCommonInBackwardsSearch = null; while (aIndex >= 0 && bIndex >= 0) { if (aSuperList.get(aIndex) != bSuperList.get(bIndex)) { break; } lastCommonInBackwardsSearch = aSuperList.get(aIndex); aIndex--; bIndex--; } if (lastCommonInBackwardsSearch == null) firstCommonSupertype = ObjectType.OBJECT; else firstCommonSupertype = ObjectTypeFactory.getInstance(lastCommonInBackwardsSearch.getClassDescriptor().toDottedClassName()); if (firstCommonSupertype.equals(ObjectType.OBJECT)) { // see if we can't do better ClassDescriptor objDesc= DescriptorFactory.getClassDescriptor(ObjectType.OBJECT); aSuperTypes.retainAll(bSuperTypes); aSuperTypes.remove(objDesc); for(ClassDescriptor c : aSuperTypes) if (c.getPackageName().equals(aDesc.getPackageName()) || c.getPackageName().equals(bDesc.getPackageName())) return ObjectTypeFactory.getInstance(c.toDottedClassName()); for(ClassDescriptor c : aSuperTypes) return ObjectTypeFactory.getInstance(c.toDottedClassName()); } return firstCommonSupertype; } private void putFirstCommonSuperclassQueryCache(ReferenceType a, ReferenceType b, ReferenceType answer) { if (a.getSignature().compareTo(b.getSignature()) > 0) { ReferenceType tmp = a; a = b; b = tmp; } firstCommonSuperclassQueryCache.put(a, b, answer); } private ReferenceType checkFirstCommonSuperclassQueryCache(ReferenceType a, ReferenceType b) { if (a.getSignature().compareTo(b.getSignature()) > 0) { ReferenceType tmp = a; a = b; b = tmp; } return firstCommonSuperclassQueryCache.get(a, b); } /** * Get list of all superclasses of class represented by given class vertex, * in order, including the class itself (which is trivially its own superclass * as far as "first common superclass" queries are concerned.) * * @param vertex a ClassVertex * @return list of all superclass vertices in order */ private ArrayList<ClassVertex> getAllSuperclassVertices(ClassVertex vertex) throws ClassNotFoundException { ArrayList<ClassVertex> result = new ArrayList<ClassVertex>(); ClassVertex cur = vertex; while (cur != null) { if (!cur.isResolved()) { ClassDescriptor.throwClassNotFoundException(cur.getClassDescriptor()); } result.add(cur); cur = cur.getDirectSuperclass(); } return result; } /** * Get known subtypes of given class. * The set returned <em>DOES</em> include the class itself. * * @param classDescriptor ClassDescriptor naming a class * @return Set of ClassDescriptors which are the known subtypes of the class * @throws ClassNotFoundException */ public Set<ClassDescriptor> getSubtypes(ClassDescriptor classDescriptor) throws ClassNotFoundException { Set<ClassDescriptor> result = subtypeSetMap.get(classDescriptor); if (result == null) { result = computeKnownSubtypes(classDescriptor); subtypeSetMap.put(classDescriptor, result); } return result; } /** * Determine whether or not the given class has any known subtypes. * * @param classDescriptor ClassDescriptor naming a class * @return true if the class has subtypes, false if it has no subtypes * @throws ClassNotFoundException */ public boolean hasSubtypes(ClassDescriptor classDescriptor) throws ClassNotFoundException { Set<ClassDescriptor> subtypes = getDirectSubtypes(classDescriptor); if (DEBUG) { System.out.println("Direct subtypes of " + classDescriptor + " are " + subtypes); } return !subtypes.isEmpty(); } /** * Get known subtypes of given class. * * @param classDescriptor ClassDescriptor naming a class * @return Set of ClassDescriptors which are the known subtypes of the class * @throws ClassNotFoundException */ public Set<ClassDescriptor> getDirectSubtypes(ClassDescriptor classDescriptor) throws ClassNotFoundException { ClassVertex startVertex = resolveClassVertex(classDescriptor); Set<ClassDescriptor> result = new HashSet<ClassDescriptor>(); Iterator<InheritanceEdge> i = graph.incomingEdgeIterator(startVertex); while (i.hasNext()) { InheritanceEdge edge = i.next(); result.add(edge.getSource().getClassDescriptor()); } return result; } /** * Get the set of common subtypes of the two given classes. * * @param classDescriptor1 a ClassDescriptor naming a class * @param classDescriptor2 a ClassDescriptor naming another class * @return Set containing all common transitive subtypes of the two classes * @throws ClassNotFoundException */ public Set<ClassDescriptor> getTransitiveCommonSubtypes(ClassDescriptor classDescriptor1, ClassDescriptor classDescriptor2) throws ClassNotFoundException { Set<ClassDescriptor> subtypes1 = getSubtypes(classDescriptor1); Set<ClassDescriptor> result = new HashSet<ClassDescriptor>(subtypes1); Set<ClassDescriptor> subtypes2 = getSubtypes(classDescriptor2); result.retainAll(subtypes2); return result; } /** * Get Collection of all XClass objects (resolved classes) * seen so far. * * @return Collection of all XClass objects */ public Collection<XClass> getXClassCollection() { return Collections.<XClass>unmodifiableCollection(xclassSet); } /** * An in-progress traversal of one path from a class or interface * to java.lang.Object. */ private static class SupertypeTraversalPath { ClassVertex next; Set<ClassDescriptor> seen; public SupertypeTraversalPath(@CheckForNull ClassVertex next) { this.next = next; this.seen = new HashSet<ClassDescriptor>(); } @Override public String toString() { return next.toString() + ":" + seen; } public ClassVertex getNext() { return next; } public boolean hasBeenSeen(ClassDescriptor classDescriptor) { return seen.contains(classDescriptor); } public void markSeen(ClassDescriptor classDescriptor) { seen.add(classDescriptor); } public void setNext(ClassVertex next) { assert !hasBeenSeen(next.getClassDescriptor()); this.next = next; } public SupertypeTraversalPath fork(ClassVertex next) { SupertypeTraversalPath dup = new SupertypeTraversalPath(null); dup.seen.addAll(this.seen); dup.setNext(next); return dup; } } /** * Starting at the class or interface named by the given ClassDescriptor, * traverse the inheritance graph, exploring all paths from * the class or interface to java.lang.Object. * * @param start ClassDescriptor naming the class where the traversal should start * @param visitor an InheritanceGraphVisitor * @throws ClassNotFoundException if the start vertex cannot be resolved */ public void traverseSupertypes(ClassDescriptor start, InheritanceGraphVisitor visitor) throws ClassNotFoundException { LinkedList<SupertypeTraversalPath> workList = new LinkedList<SupertypeTraversalPath>(); ClassVertex startVertex = resolveClassVertex(start); workList.addLast(new SupertypeTraversalPath(startVertex)); while (!workList.isEmpty()) { SupertypeTraversalPath cur = workList.removeFirst(); ClassVertex vertex = cur.getNext(); assert !cur.hasBeenSeen(vertex.getClassDescriptor()); cur.markSeen(vertex.getClassDescriptor()); if (!visitor.visitClass(vertex.getClassDescriptor(), vertex.getXClass())) { // Visitor doesn't want to continue on this path continue; } if (!vertex.isResolved()) { // Unknown class - so, we don't know its immediate supertypes continue; } // Advance to direct superclass ClassDescriptor superclassDescriptor = vertex.getXClass().getSuperclassDescriptor(); if (superclassDescriptor != null && traverseEdge(vertex, superclassDescriptor, false, visitor)) { addToWorkList(workList, cur, superclassDescriptor); } // Advance to directly-implemented interfaces for (ClassDescriptor ifaceDesc : vertex.getXClass().getInterfaceDescriptorList()) { if (traverseEdge(vertex, ifaceDesc, true, visitor)) { addToWorkList(workList, cur, ifaceDesc); } } } } private void addToWorkList( LinkedList<SupertypeTraversalPath> workList, SupertypeTraversalPath curPath, ClassDescriptor supertypeDescriptor) { ClassVertex vertex = classDescriptorToVertexMap.get(supertypeDescriptor); // The vertex should already have been added to the graph assert vertex != null; if (curPath.hasBeenSeen(vertex.getClassDescriptor())) { // This can only happen when the inheritance graph has a cycle return; } SupertypeTraversalPath newPath = curPath.fork(vertex); workList.addLast(newPath); } private boolean traverseEdge( ClassVertex vertex, @CheckForNull ClassDescriptor supertypeDescriptor, boolean isInterfaceEdge, InheritanceGraphVisitor visitor) { if (supertypeDescriptor == null) { // We reached java.lang.Object return false; } ClassVertex supertypeVertex = classDescriptorToVertexMap.get(supertypeDescriptor); if (supertypeVertex == null) { try { supertypeVertex = resolveClassVertex(supertypeDescriptor); } catch (ClassNotFoundException e) { supertypeVertex = addClassVertexForMissingClass(supertypeDescriptor, isInterfaceEdge); } } assert supertypeVertex != null; return visitor.visitEdge(vertex.getClassDescriptor(), vertex.getXClass(), supertypeDescriptor, supertypeVertex.getXClass()); } /** * Compute set of known subtypes of class named by given ClassDescriptor. * * @param classDescriptor a ClassDescriptor * @throws ClassNotFoundException */ private Set<ClassDescriptor> computeKnownSubtypes(ClassDescriptor classDescriptor) throws ClassNotFoundException { LinkedList<ClassVertex> workList = new LinkedList<ClassVertex>(); ClassVertex startVertex = resolveClassVertex(classDescriptor); workList.addLast(startVertex); Set<ClassDescriptor> result = new HashSet<ClassDescriptor>(); while (!workList.isEmpty()) { ClassVertex current = workList.removeFirst(); if (result.contains(current.getClassDescriptor())) { // Already added this class continue; } // Add class to the result result.add(current.getClassDescriptor()); // Add all known subtype vertices to the work list Iterator<InheritanceEdge> i = graph.incomingEdgeIterator(current); while (i.hasNext()) { InheritanceEdge edge = i.next(); workList.addLast(edge.getSource()); } } return result; } private Set<ClassDescriptor> computeKnownSupertypes(ClassDescriptor classDescriptor) throws ClassNotFoundException { LinkedList<ClassVertex> workList = new LinkedList<ClassVertex>(); ClassVertex startVertex = resolveClassVertex(classDescriptor); workList.addLast(startVertex); Set<ClassDescriptor> result = new HashSet<ClassDescriptor>(); while (!workList.isEmpty()) { ClassVertex current = workList.removeFirst(); if (result.contains(current.getClassDescriptor())) { // Already added this class continue; } // Add class to the result result.add(current.getClassDescriptor()); // Add all known subtype vertices to the work list Iterator<InheritanceEdge> i = graph.outgoingEdgeIterator(current); while (i.hasNext()) { InheritanceEdge edge = i.next(); workList.addLast(edge.getTarget()); } } return result; } /** * Look up or compute the SupertypeQueryResults for class * named by given ClassDescriptor. * * @param classDescriptor a ClassDescriptor * @return SupertypeQueryResults for the class named by the ClassDescriptor * @throws ClassNotFoundException */ public SupertypeQueryResults getSupertypeQueryResults(ClassDescriptor classDescriptor) { SupertypeQueryResults supertypeQueryResults = supertypeSetMap.get(classDescriptor); if (supertypeQueryResults == null) { supertypeQueryResults = computeSupertypes(classDescriptor); supertypeSetMap.put(classDescriptor, supertypeQueryResults); } return supertypeQueryResults; } /** * Compute supertypes for class named by given ClassDescriptor. * * @param classDescriptor a ClassDescriptor * @return SupertypeQueryResults containing known supertypes of the class * @throws ClassNotFoundException if the class can't be found */ private SupertypeQueryResults computeSupertypes(ClassDescriptor classDescriptor) // throws ClassNotFoundException { if (DEBUG_QUERIES) { System.out.println("Computing supertypes for " + classDescriptor.toDottedClassName()); } // Try to fully resolve the class and its superclasses/superinterfaces. ClassVertex typeVertex = optionallyResolveClassVertex(classDescriptor); // Create new empty SupertypeQueryResults. SupertypeQueryResults supertypeSet = new SupertypeQueryResults(); // Add all known superclasses/superinterfaces. // The ClassVertexes for all of them should be in the // InheritanceGraph by now. LinkedList<ClassVertex> workList = new LinkedList<ClassVertex>(); workList.addLast(typeVertex); while (!workList.isEmpty()) { ClassVertex vertex = workList.removeFirst(); supertypeSet.addSupertype(vertex.getClassDescriptor()); if (vertex.isResolved()) { if (DEBUG_QUERIES) { System.out.println(" Adding supertype " + vertex.getClassDescriptor().toDottedClassName()); } } else { if (DEBUG_QUERIES) { System.out.println( " Encountered unresolved class " + vertex.getClassDescriptor().toDottedClassName() + " in supertype query"); } supertypeSet.setEncounteredMissingClasses(true); } Iterator<InheritanceEdge> i = graph.outgoingEdgeIterator(vertex); while (i.hasNext()) { InheritanceEdge edge = i.next(); workList.addLast(edge.getTarget()); } } return supertypeSet; } /** * Resolve a class named by given ClassDescriptor and return * its resolved ClassVertex. * * @param classDescriptor a ClassDescriptor * @return resolved ClassVertex representing the class in the InheritanceGraph * @throws ClassNotFoundException if the class named by the ClassDescriptor does not exist */ private ClassVertex resolveClassVertex(ClassDescriptor classDescriptor) throws ClassNotFoundException { ClassVertex typeVertex = optionallyResolveClassVertex(classDescriptor); if (!typeVertex.isResolved()) { ClassDescriptor.throwClassNotFoundException(classDescriptor); } assert typeVertex.isResolved(); return typeVertex; } /** * @param classDescriptor * @return */ private ClassVertex optionallyResolveClassVertex(ClassDescriptor classDescriptor) { ClassVertex typeVertex = classDescriptorToVertexMap.get(classDescriptor); if (typeVertex == null) { // We have never tried to resolve this ClassVertex before. // Try to find the XClass for this class. XClass xclass = AnalysisContext.currentXFactory().getXClass(classDescriptor); if (xclass == null) { // Class we're trying to resolve doesn't exist. // XXX: unfortunately, we don't know if the missing class is a class or interface typeVertex = addClassVertexForMissingClass(classDescriptor, false); } else { // Add the class and all its superclasses/superinterfaces to the inheritance graph. // This will result in a resolved ClassVertex. typeVertex = addClassAndGetClassVertex(xclass); } } return typeVertex; } /** * Add supertype edges to the InheritanceGraph * for given ClassVertex. If any direct supertypes * have not been processed, add them to the worklist. * * @param vertex a ClassVertex whose supertype edges need to be added * @param workList work list of ClassVertexes that need to have * their supertype edges added */ private void addSupertypeEdges(ClassVertex vertex, LinkedList<XClass> workList) { XClass xclass = vertex.getXClass(); // Direct superclass ClassDescriptor superclassDescriptor = xclass.getSuperclassDescriptor(); if (superclassDescriptor != null) addInheritanceEdge(vertex, superclassDescriptor, false, workList); // Directly implemented interfaces for (ClassDescriptor ifaceDesc : xclass.getInterfaceDescriptorList()) { addInheritanceEdge(vertex, ifaceDesc, true, workList); } } /** * Add supertype edge to the InheritanceGraph. * * @param vertex source ClassVertex (subtype) * @param superclassDescriptor ClassDescriptor of a direct supertype * @param isInterfaceEdge true if supertype is (as far as we know) an interface * @param workList work list of ClassVertexes that need to have * their supertype edges added (null if no further work will be generated) */ private void addInheritanceEdge( ClassVertex vertex, ClassDescriptor superclassDescriptor, boolean isInterfaceEdge, @CheckForNull LinkedList<XClass> workList) { if (superclassDescriptor == null) { return; } ClassVertex superclassVertex = classDescriptorToVertexMap.get(superclassDescriptor); if (superclassVertex == null) { // Haven't encountered this class previously. XClass superclassXClass = AnalysisContext.currentXFactory().getXClass(superclassDescriptor); if (superclassXClass == null) { // Inheritance graph will be incomplete. // Add a dummy node to inheritance graph and report missing class. superclassVertex = addClassVertexForMissingClass(superclassDescriptor, isInterfaceEdge); } else { // Haven't seen this class before. superclassVertex = ClassVertex.createResolvedClassVertex(superclassDescriptor, superclassXClass); addVertexToGraph(superclassDescriptor, superclassVertex); if (workList != null) { // We'll want to recursively process the superclass. workList.addLast(superclassXClass); } } } assert superclassVertex != null; if (graph.lookupEdge(vertex, superclassVertex) == null) { if (DEBUG) { System.out.println(" Add edge " + vertex.getClassDescriptor().toDottedClassName() + " -> " + superclassDescriptor.toDottedClassName()); } graph.createEdge(vertex, superclassVertex); } } /** * Add a ClassVertex representing a missing class. * * @param missingClassDescriptor ClassDescriptor naming a missing class * @param isInterfaceEdge * @return the ClassVertex representing the missing class */ private ClassVertex addClassVertexForMissingClass(ClassDescriptor missingClassDescriptor, boolean isInterfaceEdge) { ClassVertex missingClassVertex = ClassVertex.createMissingClassVertex(missingClassDescriptor, isInterfaceEdge); missingClassVertex.setFinished(true); addVertexToGraph(missingClassDescriptor, missingClassVertex); AnalysisContext.currentAnalysisContext().reportMissingClass(missingClassDescriptor); return missingClassVertex; } }