/******************************************************************************* * Copyright (c) 2002 - 2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.ipa.cha; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.ibm.wala.classLoader.ArrayClass; import com.ibm.wala.classLoader.ClassLoaderFactory; import com.ibm.wala.classLoader.ClassLoaderFactoryImpl; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IClassLoader; import com.ibm.wala.classLoader.IField; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.classLoader.Language; import com.ibm.wala.classLoader.ShrikeClass; import com.ibm.wala.ipa.callgraph.AnalysisScope; import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.types.FieldReference; import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.Selector; import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.MonitorUtil.IProgressMonitor; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.collections.Iterator2Collection; import com.ibm.wala.util.collections.MapIterator; import com.ibm.wala.util.collections.MapUtil; import com.ibm.wala.util.debug.Assertions; import com.ibm.wala.util.debug.UnimplementedError; import com.ibm.wala.util.functions.Function; import com.ibm.wala.util.ref.CacheReference; import com.ibm.wala.util.ref.ReferenceCleanser; import com.ibm.wala.util.strings.Atom; import com.ibm.wala.util.warnings.Warning; import com.ibm.wala.util.warnings.Warnings; /** * Simple implementation of a class hierarchy. * * Note that this class hierarchy implementation is mutable. You can add classes via addClass(). You can add a class even if * c.getClassLoader() does not appear in getLoaders(). */ public class ClassHierarchy implements IClassHierarchy { private static final boolean DEBUG = false; /** * Languages that contribute classes to the set represented in this hierarchy. The languages may for example be related by * inheritance (e.g. X10 derives from Java, and shares a common type hierarchy rooted at java.lang.Object). */ private final Set<Language> languages = HashSetFactory.make(); /** * For each {@link IClass} c in this class hierarchy, this map maps c.getReference() to the {@link Node} * * Note that this class provides an iterator() over this map, and that some WALA utilities (e.g. ReferenceCleanser) must iterate * over all classes. But also note that the class hierarchy is mutable (addClass()). So, when trying to run multiple threads, we * could see a race condition between iterator() and addClass(). With a normal {@link HashMap}, this would result in a * {@link ConcurrentModificationException}. But with a {@link ConcurrentHashMap}, at least the code merrily chugs along, * tolerating the race. */ final private Map<TypeReference, Node> map = new ConcurrentHashMap<TypeReference, Node>(); /** * {@link TypeReference} for the root type */ private TypeReference rootTypeRef; /** * root node of the class hierarchy */ private Node root; /** * An object which defines class loaders. */ final private ClassLoaderFactory factory; /** * The loaders used to define this class hierarchy. */ final private IClassLoader[] loaders; /** * A mapping from IClass -> Selector -> Set of IMethod */ final private HashMap<IClass, Object> targetCache = HashMapFactory.make(); /** * Governing analysis scope */ private final AnalysisScope scope; /** * A mapping from IClass (representing an interface) -> Set of IClass that implement that interface */ private final Map<IClass, Set<IClass>> implementors = HashMapFactory.make(); /** * A temporary hack : TODO: do intelligent caching somehow */ private Collection<IClass> subclassesOfError; /** * A temporary hack : TODO: do intelligent caching somehow */ private Collection<TypeReference> subTypeRefsOfError; /** * A temporary hack : TODO: do intelligent caching somehow */ private Collection<IClass> runtimeExceptionClasses; /** * A temporary hack : TODO: do intelligent caching somehow */ private Collection<TypeReference> runtimeExceptionTypeRefs; /** * Return a set of {@link IClass} that holds all superclasses of klass * * @param klass class in question * @return Set the result set */ private Set<IClass> computeSuperclasses(IClass klass) { if (DEBUG) { System.err.println("computeSuperclasses: " + klass); } Set<IClass> result = HashSetFactory.make(3); klass = klass.getSuperclass(); while (klass != null) { if (DEBUG) { System.err.println("got superclass " + klass); } boolean added = result.add(klass); if (!added) { // oops. we have A is a sub-class of B and B is a sub-class of A. blow up. throw new IllegalStateException("cycle in the extends relation for class " + klass); } klass = klass.getSuperclass(); if (klass != null && klass.getReference().getName().equals(rootTypeRef.getName())) { if (!klass.getReference().getClassLoader().equals(rootTypeRef.getClassLoader())) { throw new IllegalStateException("class " + klass + " is invalid, unexpected classloader"); } } } return result; } private ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, Language language, IProgressMonitor progressMonitor) throws ClassHierarchyException, IllegalArgumentException { this(scope, factory, Collections.singleton(language), progressMonitor); } private ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, IProgressMonitor progressMonitor) throws ClassHierarchyException, IllegalArgumentException { this(scope, factory, scope.getLanguages(), progressMonitor); } private ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, Collection<Language> languages, IProgressMonitor progressMonitor) throws ClassHierarchyException, IllegalArgumentException { // now is a good time to clear the warnings globally. // TODO: think of a better way to guard against warning leaks. Warnings.clear(); if (factory == null) { throw new IllegalArgumentException(); } if (scope.getLanguages().size() == 0) { throw new IllegalArgumentException("AnalysisScope must contain at least 1 language"); } this.scope = scope; this.factory = factory; Set<Atom> langNames = HashSetFactory.make(); for (Language lang : languages) { this.languages.add(lang); this.languages.addAll(lang.getDerivedLanguages()); langNames.add(lang.getName()); } for (Language lang : this.languages) { if (lang.getRootType() != null && lang.getRootType() != this.rootTypeRef) { if (this.rootTypeRef != null) { throw new IllegalArgumentException("AnalysisScope must have only 1 root type: " + lang.getRootType() + ", " + rootTypeRef); } else { this.rootTypeRef = lang.getRootType(); } } } try { int numLoaders = 0; for (ClassLoaderReference ref : scope.getLoaders()) { if (langNames.contains(ref.getLanguage())) { numLoaders++; } } loaders = new IClassLoader[numLoaders]; int idx = 0; if (progressMonitor != null) { progressMonitor.beginTask("Build Class Hierarchy", (numLoaders) * 2 - 1); } for (ClassLoaderReference ref : scope.getLoaders()) { if (progressMonitor != null) { if (progressMonitor.isCanceled()) { throw new CancelCHAConstructionException(); } } if (langNames.contains(ref.getLanguage())) { IClassLoader icl = factory.getLoader(ref, this, scope); loaders[idx++] = icl; if (progressMonitor != null) { progressMonitor.worked(idx); } } } for (IClassLoader icl : loaders) { if (progressMonitor != null) { progressMonitor.subTask("From " + icl.getName().toString()); } addAllClasses(icl, progressMonitor); if (progressMonitor != null) { progressMonitor.worked(idx++); } } } catch (IOException e) { throw new ClassHierarchyException("factory.getLoader failed " + e); } finally { if (progressMonitor != null) { progressMonitor.done(); // In case an exception is thrown. } } if (root == null) { throw new ClassHierarchyException("failed to load root " + rootTypeRef + " of class hierarchy"); } // perform numbering for subclass tests. numberTree(); ReferenceCleanser.registerClassHierarchy(this); } /** * Add all classes in a class loader to the hierarchy. */ private void addAllClasses(IClassLoader loader, IProgressMonitor progressMonitor) throws CancelCHAConstructionException { if (DEBUG) { System.err.println(("Add all classes from loader " + loader)); } Collection<IClass> toRemove = HashSetFactory.make(); for (Iterator<IClass> it = loader.iterateAllClasses(); it.hasNext();) { if (progressMonitor != null) { if (progressMonitor.isCanceled()) { throw new CancelCHAConstructionException(); } } IClass klass = it.next(); boolean added = addClass(klass); if (!added) { toRemove.add(klass); } } loader.removeAll(toRemove); } /** * @return true if the add succeeded; false if it failed for some reason * @throws IllegalArgumentException if klass is null */ @Override public boolean addClass(IClass klass) { if (klass == null) { throw new IllegalArgumentException("klass is null"); } if (klass.getReference().getName().equals(rootTypeRef.getName())) { if (!klass.getReference().getClassLoader().equals(rootTypeRef.getClassLoader())) { throw new IllegalArgumentException("class " + klass + " is invalid, unexpected classloader"); } } if (DEBUG) { System.err.println(("Attempt to add class " + klass)); } Set<IClass> loadedSuperclasses; Collection loadedSuperInterfaces; try { loadedSuperclasses = computeSuperclasses(klass); loadedSuperInterfaces = klass.getAllImplementedInterfaces(); } catch (Exception e) { // a little cleanup if (klass instanceof ShrikeClass) { if (DEBUG) { System.err.println(("Exception. Clearing " + klass)); } } Warnings.add(ClassExclusion.create(klass.getReference(), e.getMessage())); return false; } Node node = findOrCreateNode(klass); if (klass.getReference().equals(this.rootTypeRef)) { // there is only one root assert root == null; root = node; } Set workingSuperclasses = HashSetFactory.make(loadedSuperclasses); while (node != null) { IClass c = node.getJavaClass(); IClass superclass = null; superclass = c.getSuperclass(); if (superclass != null) { workingSuperclasses.remove(superclass); Node supernode = findOrCreateNode(superclass); if (DEBUG) { System.err.println(("addChild " + node.getJavaClass() + " to " + supernode.getJavaClass())); } supernode.addChild(node); if (supernode.getJavaClass().getReference().equals(rootTypeRef)) { node = null; } else { node = supernode; } } else { node = null; } } if (loadedSuperInterfaces != null) { for (Iterator it3 = loadedSuperInterfaces.iterator(); it3.hasNext();) { final IClass iface = (IClass) it3.next(); try { // make sure we'll be able to load the interface! computeSuperclasses(iface); } catch (IllegalStateException e) { Warnings.add(ClassExclusion.create(iface.getReference(), e.getMessage())); continue; } if (!iface.isInterface()) { Warnings.add(new Warning() { @Override public String getMsg() { return "class implements non-interface " + iface.getReference() + " as an interface"; } }); continue; } recordImplements(klass, iface); } } return true; } /** * Record that a klass implements a particular interface */ private void recordImplements(IClass klass, IClass iface) { Set<IClass> impls = MapUtil.findOrCreateSet(implementors, iface); impls.add(klass); } /** * Find the possible targets of a call to a method reference. Note that if the reference is to an instance initialization method, * we assume the method was called with invokespecial rather than invokevirtual. * * @param ref method reference * @return the set of IMethods that this call can resolve to. * @throws IllegalArgumentException if ref is null */ @Override public Set<IMethod> getPossibleTargets(MethodReference ref) { if (ref == null) { throw new IllegalArgumentException("ref is null"); } IClassLoader loader; try { loader = factory.getLoader(ref.getDeclaringClass().getClassLoader(), this, scope); } catch (IOException e) { throw new UnimplementedError("factory.getLoader failed " + e); } IClass declaredClass; declaredClass = loader.lookupClass(ref.getDeclaringClass().getName()); if (declaredClass == null) { return Collections.emptySet(); } Set<IMethod> targets = HashSetFactory.make(); targets.addAll(findOrCreateTargetSet(declaredClass, ref)); return (targets); } /** * Find the possible targets of a call to a method reference * * @param ref method reference * @return the set of IMethods that this call can resolve to. */ @SuppressWarnings("unchecked") private Set<IMethod> findOrCreateTargetSet(IClass declaredClass, MethodReference ref) { Map<MethodReference, Set<IMethod>> classCache = (Map<MethodReference, Set<IMethod>>) CacheReference.get(targetCache .get(declaredClass)); if (classCache == null) { classCache = HashMapFactory.make(3); targetCache.put(declaredClass, CacheReference.make(classCache)); } Set<IMethod> result = classCache.get(ref); if (result == null) { result = getPossibleTargets(declaredClass, ref); classCache.put(ref, result); } return result; } /** * Find the possible receivers of a call to a method reference * * @param ref method reference * @return the set of IMethods that this call can resolve to. */ @Override public Set<IMethod> getPossibleTargets(IClass declaredClass, MethodReference ref) { if (ref.getName().equals(MethodReference.initAtom)) { // for an object init method, use the method alone as a possible target, // rather than inspecting subclasses IMethod resolvedMethod = resolveMethod(ref); assert resolvedMethod != null; return Collections.singleton(resolvedMethod); } if (declaredClass.isInterface()) { HashSet<IMethod> result = HashSetFactory.make(3); Set impls = implementors.get(declaredClass); if (impls == null) { // give up and return no receivers return Collections.emptySet(); } for (Iterator it = impls.iterator(); it.hasNext();) { IClass klass = (IClass) it.next(); if (!klass.isInterface() && !klass.isAbstract()) { result.addAll(computeTargetsNotInterface(ref, klass)); } } return result; } else { return computeTargetsNotInterface(ref, declaredClass); } } /** * Get the targets for a method ref invoked on a class klass. The klass had better not be an interface. * * @param ref method to invoke * @param klass declaringClass of receiver * @return Set the set of method implementations that might receive the message */ private Set<IMethod> computeTargetsNotInterface(MethodReference ref, IClass klass) { Node n = findNode(klass); HashSet<IMethod> result = HashSetFactory.make(3); // if n is null, then for some reason this class is excluded // from the analysis. Return a result of no targets. if (n == null) return result; Selector selector = ref.getSelector(); // try to resolve the method by walking UP the class hierarchy IMethod resolved = resolveMethod(klass, selector); if (resolved != null) { result.add(resolved); } // find any receivers that override the method with inheritance result.addAll(computeOverriders(n, selector)); return result; } /** * Return the unique receiver of an invocation of method on an object of type m.getDeclaredClass * * @param m * @return IMethod, or null if no appropriate receiver is found. * @throws IllegalArgumentException if m is null */ @Override public IMethod resolveMethod(MethodReference m) { if (m == null) { throw new IllegalArgumentException("m is null"); } IClass receiver = lookupClass(m.getDeclaringClass()); if (receiver == null) { return null; } Selector selector = m.getSelector(); return resolveMethod(receiver, selector); } /** * @return the canonical IField that represents a given field , or null if none found * @throws IllegalArgumentException if f is null */ @Override public IField resolveField(FieldReference f) { if (f == null) { throw new IllegalArgumentException("f is null"); } IClass klass = lookupClass(f.getDeclaringClass()); if (klass == null) { return null; } return resolveField(klass, f); } /** * @return the canonical IField that represents a given field , or null if none found * @throws IllegalArgumentException if f is null * @throws IllegalArgumentException if klass is null */ @Override public IField resolveField(IClass klass, FieldReference f) { if (klass == null) { throw new IllegalArgumentException("klass is null"); } if (f == null) { throw new IllegalArgumentException("f is null"); } return klass.getField(f.getName(), f.getFieldType().getName()); } /** * Return the unique target of an invocation of method on an object of type declaringClass * * @param receiverClass type of receiver * @param selector method signature * @return Method resolved method abstraction * @throws IllegalArgumentException if receiverClass is null */ @Override public IMethod resolveMethod(IClass receiverClass, Selector selector) { if (receiverClass == null) { throw new IllegalArgumentException("receiverClass is null"); } IMethod result = findMethod(receiverClass, selector); if (result != null) { return result; } else { IClass superclass = null; superclass = receiverClass.getSuperclass(); if (superclass == null) { if (DEBUG) { System.err.println(("resolveMethod(" + selector + ") failed: method not found")); } return null; } else { if (DEBUG) { System.err.println(("Attempt to resolve for " + receiverClass + " in superclass: " + superclass + " " + selector)); } return resolveMethod(superclass, selector); } } } /** * Does a particular class contain (implement) a particular method? * * @param clazz class in question * @param selector method selector * @return the method if found, else null */ private IMethod findMethod(IClass clazz, Selector selector) { return clazz.getMethod(selector); } /** * Get the set of subclasses of a class that provide implementations of a method * * @param node abstraction of class in question * @param selector method signature * @return Set set of IMethods that override the method */ private Set<IMethod> computeOverriders(Node node, Selector selector) { HashSet<IMethod> result = HashSetFactory.make(3); for (Iterator<Node> it = node.getChildren(); it.hasNext();) { Node child = it.next(); IMethod m = findMethod(child.getJavaClass(), selector); if (m != null) { result.add(m); } result.addAll(computeOverriders(child, selector)); } return result; } private Node findNode(IClass klass) { return map.get(klass.getReference()); } private Node findOrCreateNode(IClass klass) { Node result = map.get(klass.getReference()); if (result == null) { result = new Node(klass); map.put(klass.getReference(), result); } return result; } @Override public String toString() { StringBuffer result = new StringBuffer(100); recursiveStringify(root, result); return result.toString(); } private void recursiveStringify(Node n, StringBuffer buffer) { buffer.append(n.toString()).append("\n"); for (Iterator<Node> it = n.getChildren(); it.hasNext();) { Node child = it.next(); recursiveStringify(child, buffer); } } /** * Number the class hierarchy tree to support efficient subclass tests. After numbering the tree, n1 is a child of n2 iff n2.left * <= n1.left ^ n1.left <= n2.right. Described as "relative numbering" by Vitek, Horspool, and Krall, OOPSLA 97 * * TODO: this implementation is recursive; un-recursify if needed */ private int nextNumber = 1; private void numberTree() { assert root != null; visitForNumbering(root); } private void visitForNumbering(Node N) { N.left = nextNumber++; for (Iterator<Node> it = N.children.iterator(); it.hasNext();) { Node C = it.next(); visitForNumbering(C); } N.right = nextNumber++; } /** * internal representation of a node in the class hiearachy, representing one java class. */ private static final class Node { private final IClass klass; final private Set<Node> children = HashSetFactory.make(3); // the following two fields are used for efficient subclass tests. // After numbering the tree, n1 is a child of n2 iff // n2.left <= n1.left ^ n1.left <= n2.right. // Described as "relative numbering" by Vitek, Horspool, and Krall, OOPSLA // 97 private int left = -1; private int right = -1; Node(IClass klass) { this.klass = klass; } boolean isInterface() { return klass.isInterface(); } IClass getJavaClass() { return klass; } void addChild(Node child) { children.add(child); } Iterator<Node> getChildren() { return children.iterator(); } @Override public String toString() { StringBuffer result = new StringBuffer(100); result.append(klass.toString()).append(":"); for (Iterator<Node> i = children.iterator(); i.hasNext();) { Node n = i.next(); result.append(n.klass.toString()); if (i.hasNext()) result.append(","); } return result.toString(); } @Override public int hashCode() { return klass.hashCode() * 929; } @Override public boolean equals(Object obj) { return this == obj; } } @Override public ClassLoaderFactory getFactory() { return factory; } /** * @throws IllegalArgumentException if A is null */ @Override public IClass getLeastCommonSuperclass(IClass a, IClass b) { assert (a.getClassLoader().getLanguage().equals(b.getClassLoader().getLanguage())); Language lang = a.getClassLoader().getLanguage(); TypeReference tempA = a.getReference(); if (a.equals(b)) { return a; } else if (tempA.equals(TypeReference.Null)) { return b; } else if (b.getReference().equals(TypeReference.Null)) { return a; } else if (b.getReference().equals(lang.getRootType())) { return b; } else { Node n = map.get(b.getReference()); if (n == null) { assert n != null : "null n for " + b; } Set<IClass> superB; try { superB = getSuperclasses(b); } catch (ClassHierarchyException e1) { e1.printStackTrace(); Assertions.UNREACHABLE(); superB = null; } IClass aa = a; while (aa != null) { if (b.equals(aa) || superB.contains(aa)) { return aa; } aa = aa.getSuperclass(); } Set<IClass> superA; try { superA = getSuperclasses(a); } catch (ClassHierarchyException e1) { e1.printStackTrace(); Assertions.UNREACHABLE(); superA = null; } Assertions.UNREACHABLE("getLeastCommonSuperclass " + tempA + " " + b + ": " + superA + ", " + superB); return null; } } private Set<IClass> getSuperclasses(IClass c) throws ClassHierarchyException { HashSet<IClass> result = HashSetFactory.make(3); while (c.getSuperclass() != null) { result.add(c.getSuperclass()); c = c.getSuperclass(); } return result; } /* * @see com.ibm.wala.ipa.cha.IClassHierarchy#getLeastCommonSuperclass(com.ibm.wala.types.TypeReference, * com.ibm.wala.types.TypeReference) */ @Override public TypeReference getLeastCommonSuperclass(TypeReference a, TypeReference b) { if (a == null) { throw new IllegalArgumentException("a is null"); } if (a.equals(b)) return a; IClass aClass = lookupClass(a); IClass bClass = lookupClass(b); if (aClass == null || bClass == null) { // One of the classes is not in scope. Give up. if (aClass != null) { return aClass.getClassLoader().getLanguage().getRootType(); } else if (bClass != null) { return bClass.getClassLoader().getLanguage().getRootType(); } else { return getRootClass().getReference(); } } return getLeastCommonSuperclass(aClass, bClass).getReference(); } /** * Find a class in this class hierarchy. * * @return the {@link IClass} for a if found; null if can't find the class. * @throws IllegalArgumentException if A is null */ @Override public IClass lookupClass(TypeReference a) { if (a == null) { throw new IllegalArgumentException("a is null"); } /** BEGIN Custom change: remember unresolved classes */ final IClass cls = lookupClassRecursive(a); if (cls == null) { unresolved.add(a); } return cls; } private IClass lookupClassRecursive(TypeReference a) { /** END Custom change: remember unresolved classes */ ClassLoaderReference loader = a.getClassLoader(); ClassLoaderReference parent = loader.getParent(); // first delegate lookup to the parent loader. if (parent != null) { TypeReference p = TypeReference.findOrCreate(parent, a.getName()); IClass c = lookupClassRecursive(p); if (c != null) { return c; } } // lookup in the parent failed. lookup based on the declared loader of a. if (a.isArrayType()) { TypeReference elt = a.getInnermostElementType(); if (elt.isPrimitiveType()) { // look it up with the primordial loader. return getRootClass().getClassLoader().lookupClass(a.getName()); } else { IClass c = lookupClassRecursive(elt); if (c == null) { // can't load the element class, so give up. return null; } else { // we know it comes from c's class loader. return c.getClassLoader().lookupClass(a.getName()); } } } else { Node n = map.get(a); if (n != null) { return n.klass; } else { return null; } } } private boolean slowIsSubclass(IClass sub, IClass sup) { if (sub == sup) { return true; } else { IClass parent = sub.getSuperclass(); if (parent == null) { return false; } else { return slowIsSubclass(parent, sup); } } } /** * Is c a subclass of T? * * @throws IllegalArgumentException if c is null */ @Override public boolean isSubclassOf(IClass c, IClass t) { if (c == null) { throw new IllegalArgumentException("c is null"); } assert t != null : "null T"; if (c.isArrayClass()) { if (t.getReference() == TypeReference.JavaLangObject) { return true; } else if (t.getReference().isArrayType()) { TypeReference elementType = t.getReference().getArrayElementType(); if (elementType.isPrimitiveType()) { return elementType.equals(c.getReference().getArrayElementType()); } else { IClass elementKlass = lookupClass(elementType); if (elementKlass == null) { // uh oh. Warnings.add(ClassHierarchyWarning.create("could not find " + elementType)); return false; } IClass ce = ((ArrayClass) c).getElementClass(); if (ce == null) { return false; } if (elementKlass.isInterface()) { return implementsInterface(ce, elementKlass); } else { return isSubclassOf(ce, elementKlass); } } } else { return false; } } else { if (t.getReference().isArrayType()) { return false; } if (c.getReference().equals(t.getReference())) { return true; } Node n1 = map.get(c.getReference()); if (n1 == null) { // some wacky case, like a FakeRootClass return false; } Node n2 = map.get(t.getReference()); if (n2 == null) { // some wacky case, like a FakeRootClass return false; } if (n1.left == -1) { return slowIsSubclass(c, t); } else if (n2.left == -1) { return slowIsSubclass(c, t); } else { return (n2.left <= n1.left) && (n1.left <= n2.right); } } } /** * Does c implement i? * * @return true iff i is an interface and c is a class that implements i, or c is an interface that extends i. */ @Override public boolean implementsInterface(IClass c, IClass i) { if (i == null) { throw new IllegalArgumentException("Cannot ask implementsInterface with i == null"); } if (c == null) { throw new IllegalArgumentException("Cannot ask implementsInterface with c == null"); } if (!i.isInterface()) { return false; } if (c.equals(i)) { return true; } if (c.isArrayClass()) { // arrays implement Cloneable and Serializable return i.equals(lookupClass(TypeReference.JavaLangCloneable)) || i.equals(lookupClass(TypeReference.JavaIoSerializable)); } Set impls = implementors.get(i); if (impls != null && impls.contains(c)) { return true; } return false; } /** * Return set of all subclasses of type in the Class Hierarchy TODO: Tune this implementation. Consider caching if necessary. */ @Override public Collection<IClass> computeSubClasses(TypeReference type) { IClass t = lookupClass(type); if (t == null) { throw new IllegalArgumentException("could not find class for TypeReference " + type); } // a hack: TODO: work on better caching if (t.getReference().equals(TypeReference.JavaLangError)) { if (subclassesOfError == null) { subclassesOfError = computeSubClassesInternal(t); } return subclassesOfError; } else if (t.getReference().equals(TypeReference.JavaLangRuntimeException)) { if (runtimeExceptionClasses == null) { runtimeExceptionClasses = computeSubClassesInternal(t); } return runtimeExceptionClasses; } else { return computeSubClassesInternal(t); } } /** * Solely for optimization; return a Collection<TypeReference> representing the subclasses of Error * * kind of ugly. a better scheme? */ @Override public Collection<TypeReference> getJavaLangErrorTypes() { if (subTypeRefsOfError == null) { computeSubClasses(TypeReference.JavaLangError); subTypeRefsOfError = HashSetFactory.make(subclassesOfError.size()); for (Iterator it = subclassesOfError.iterator(); it.hasNext();) { IClass klass = (IClass) it.next(); subTypeRefsOfError.add(klass.getReference()); } } return Collections.unmodifiableCollection(subTypeRefsOfError); } /** * Solely for optimization; return a Collection<TypeReference> representing the subclasses of RuntimeException * * kind of ugly. a better scheme? */ @Override public Collection<TypeReference> getJavaLangRuntimeExceptionTypes() { if (runtimeExceptionTypeRefs == null) { computeSubClasses(TypeReference.JavaLangRuntimeException); runtimeExceptionTypeRefs = HashSetFactory.make(runtimeExceptionClasses.size()); for (Iterator it = runtimeExceptionClasses.iterator(); it.hasNext();) { IClass klass = (IClass) it.next(); runtimeExceptionTypeRefs.add(klass.getReference()); } } return Collections.unmodifiableCollection(runtimeExceptionTypeRefs); } /** * Return set of all subclasses of type in the Class Hierarchy TODO: Tune this implementation. Consider caching if necessary. * * @return Set of IClasses */ private Set<IClass> computeSubClassesInternal(IClass T) { if (T.isArrayClass()) { return Collections.singleton(T); } Node node = findNode(T); assert node != null : "null node for class " + T; HashSet<IClass> result = HashSetFactory.make(3); result.add(T); for (Iterator<Node> it = node.getChildren(); it.hasNext();) { Node child = it.next(); result.addAll(computeSubClasses(child.klass.getReference())); } return result; } @Override public boolean isInterface(TypeReference type) { IClass T = lookupClass(type); assert T != null : "Null lookup for " + type; return T.isInterface(); } /** * TODO: tune this if necessary * * @param type an interface * @return Set of IClass that represent implementors of the interface */ @Override public Set<IClass> getImplementors(TypeReference type) { IClass T = lookupClass(type); Set<IClass> result = implementors.get(T); if (result == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(result); } @Override public Iterator<IClass> iterator() { Function<Node, IClass> toClass = new Function<Node, IClass>() { @Override public IClass apply(Node n) { return n.klass; } }; return new MapIterator<Node, IClass>(map.values().iterator(), toClass); } /** * @return The number of classes present in the class hierarchy. */ @Override public int getNumberOfClasses() { return map.keySet().size(); } @Override public IClassLoader[] getLoaders() { return loaders; } @Override public IClassLoader getLoader(ClassLoaderReference loaderRef) { for (int i = 0; i < loaders.length; i++) { if (loaders[i].getReference().equals(loaderRef)) { return loaders[i]; } } Assertions.UNREACHABLE(); return null; } @Override public AnalysisScope getScope() { return scope; } /** * @return the number of classes that immediately extend klass. if klass is an array class A[][]...[], we return number of * immediate subclasses of A. If A is primitive, we return 0. */ @Override public int getNumberOfImmediateSubclasses(IClass klass) { if (klass.isArrayClass()) { IClass innermost = getInnermostTypeOfArrayClass(klass); return innermost == null ? 0 : getNumberOfImmediateSubclasses(innermost); } Node node = findNode(klass); return node.children.size(); } /** * @param klass * @return the classes that immediately extend klass. if klass is an array class A[][]...[], we return array classes B[][]...[] * (same dimensionality) where B is an immediate subclass of A. If A is primitive, we return the empty set. */ @Override public Collection<IClass> getImmediateSubclasses(IClass klass) { if (klass.isArrayClass()) { return getImmediateArraySubclasses((ArrayClass)klass); } Function<Node, IClass> node2Class = new Function<Node, IClass>() { @Override public IClass apply(Node n) { return n.klass; } }; return Iterator2Collection.toSet(new MapIterator<Node, IClass>(findNode(klass).children.iterator(), node2Class)); } private Collection<IClass> getImmediateArraySubclasses(ArrayClass klass) { IClass innermost = getInnermostTypeOfArrayClass(klass); if (innermost == null) { return Collections.emptySet(); } Collection<IClass> innermostSubclasses = getImmediateSubclasses(innermost); int dim = klass.getDimensionality(); Collection<IClass> result = HashSetFactory.make(); for (IClass k : innermostSubclasses) { TypeReference ref = k.getReference(); for (int i = 0; i < dim; i++) { ref = ref.getArrayTypeForElementType(); } result.add(lookupClass(ref)); } return result; } /** * for an array class, get the innermost type, or null if it's primitive */ private IClass getInnermostTypeOfArrayClass(IClass klass) { TypeReference result = klass.getReference(); while (result.isArrayType()) { result = result.getArrayElementType(); } return result.isPrimitiveType() ? null : lookupClass(result); } /** * @return a ClassHierarchy object representing the analysis scope * @throws ClassHierarchyException */ public static ClassHierarchy make(AnalysisScope scope) throws ClassHierarchyException { if (scope == null) { throw new IllegalArgumentException("null scope"); } return make(scope, new ClassLoaderFactoryImpl(scope.getExclusions())); } /** * temporarily marking this internal to avoid infinite sleep with randomly chosen IProgressMonitor. */ public static ClassHierarchy make(AnalysisScope scope, IProgressMonitor monitor) throws ClassHierarchyException { if (scope == null) { throw new IllegalArgumentException("null scope"); } return make(scope, new ClassLoaderFactoryImpl(scope.getExclusions()), monitor); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory) throws ClassHierarchyException { if (scope == null) { throw new IllegalArgumentException("null scope"); } if (factory == null) { throw new IllegalArgumentException("null factory"); } return new ClassHierarchy(scope, factory, null); } /** * temporarily marking this internal to avoid infinite sleep with randomly chosen IProgressMonitor. */ public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, IProgressMonitor monitor) throws ClassHierarchyException { return new ClassHierarchy(scope, factory, monitor); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Set<Language> languages) throws ClassHierarchyException { return new ClassHierarchy(scope, factory, languages, null); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Language language) throws ClassHierarchyException { return new ClassHierarchy(scope, factory, language, null); } /** * temporarily marking this internal to avoid infinite sleep with randomly chosen IProgressMonitor. TODO: nanny for testgen */ public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Language language, IProgressMonitor monitor) throws ClassHierarchyException { if (factory == null) { throw new IllegalArgumentException("null factory"); } return new ClassHierarchy(scope, factory, language, monitor); } @Override public IClass getRootClass() { return root.getJavaClass(); } @Override public boolean isRootClass(IClass c) throws IllegalArgumentException { if (c == null) { throw new IllegalArgumentException("c == null"); } return c.equals(root.getJavaClass()); } @Override public int getNumber(IClass c) { return map.get(c.getReference()).left; } /** * A warning for when we fail to resolve the type for a checkcast */ private static class ClassExclusion extends Warning { final TypeReference klass; final String message; ClassExclusion(TypeReference klass, String message) { super(Warning.MODERATE); this.klass = klass; this.message = message; } @Override public String getMsg() { return getClass().toString() + " : " + klass + " " + message; } public static ClassExclusion create(TypeReference klass, String message) { return new ClassExclusion(klass, message); } } /** * Does an expression c1 x := c2 y typecheck? * * i.e. is c2 a subtype of c1? * * @throws IllegalArgumentException if c1 is null * @throws IllegalArgumentException if c2 is null */ @Override public boolean isAssignableFrom(IClass c1, IClass c2) { if (c2 == null) { throw new IllegalArgumentException("c2 is null"); } if (c1 == null) { throw new IllegalArgumentException("c1 is null"); } if (c1.isInterface()) { return implementsInterface(c2, c1); } else { if (c2.isInterface()) { return c1.equals(getRootClass()); } else { return isSubclassOf(c2, c1); } } } /** BEGIN Custom change: remember unresolved classes */ private final Set<TypeReference> unresolved = HashSetFactory.make(); @Override public final Set<TypeReference> getUnresolvedClasses() { return unresolved; } /** END Custom change: remember unresolved classes */ }