From aeb17dfca4b822c2ed99ef60d0deb3de5bf2800c Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Thu, 19 Jul 2018 16:10:35 +0200 Subject: [PATCH] Allow for classes with missing superclasses in class hierarchy (#329) Fixes #322 We add an option `createPhantomSuperclasses` to `ClassHierarchy`. When set, if a superclass is missing, we create a new `PhantomClass` in its place and allow the subclass to be added. To use, you can create the `ClassHierarchy` with the new `ClassHierarchyFactory.makeWithPhantom` methods. --- .../classes/missingsuper/.gitignore | 1 + .../classes/missingsuper/MissingSuper.class | Bin 0 -> 309 bytes .../wala/core/tests/cha/MissingSuperTest.java | 65 +++++++++ .../ibm/wala/classLoader/BytecodeClass.java | 7 +- .../NoSuperclassFoundException.java | 14 ++ .../ibm/wala/classLoader/PhantomClass.java | 108 +++++++++++++++ .../com/ibm/wala/ipa/cha/ClassHierarchy.java | 130 ++++++++++++------ .../wala/ipa/cha/ClassHierarchyFactory.java | 41 ++++-- .../ipa/cha/SeqClassHierarchyFactory.java | 17 ++- 9 files changed, 323 insertions(+), 60 deletions(-) create mode 100644 com.ibm.wala.core.testdata/classes/missingsuper/.gitignore create mode 100644 com.ibm.wala.core.testdata/classes/missingsuper/MissingSuper.class create mode 100644 com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/cha/MissingSuperTest.java create mode 100644 com.ibm.wala.core/src/com/ibm/wala/classLoader/NoSuperclassFoundException.java create mode 100644 com.ibm.wala.core/src/com/ibm/wala/classLoader/PhantomClass.java diff --git a/com.ibm.wala.core.testdata/classes/missingsuper/.gitignore b/com.ibm.wala.core.testdata/classes/missingsuper/.gitignore new file mode 100644 index 000000000..a3f6283ee --- /dev/null +++ b/com.ibm.wala.core.testdata/classes/missingsuper/.gitignore @@ -0,0 +1 @@ +!/*.class diff --git a/com.ibm.wala.core.testdata/classes/missingsuper/MissingSuper.class b/com.ibm.wala.core.testdata/classes/missingsuper/MissingSuper.class new file mode 100644 index 0000000000000000000000000000000000000000..92b53a55662fa28a9e2d4722a9f6fe1abd07b3a9 GIT binary patch literal 309 zcmX^0Z`VEs1_l!b0WJn+23~dsK6VCvMg}&U%)HDJJ4Oa(4b3n{1{UZ1lvG9rexJ;| zRKL>Pq|~C2#H1Xc2wQq;NmybIBLk~Ol&7XABLi=6Nn&!gZ(;#d0atK-X;E^jTP8@g zpl@bzab{k6aA`qmkzQ6}St1Vu3j-@7gJdp5q8KEhkF1K3K?p?-td&8LfeGkZ5MW{e zx{iSrNU{NWvOt;VP# declaredMethods = klass.getDeclaredMethods(); + Assert.assertEquals(declaredMethods.toString(), 2, declaredMethods.size()); + for (IMethod m : declaredMethods) { + // should succeed + cache.getIR(m); + } + + } +} diff --git a/com.ibm.wala.core/src/com/ibm/wala/classLoader/BytecodeClass.java b/com.ibm.wala.core/src/com/ibm/wala/classLoader/BytecodeClass.java index 15ff5ce4e..ebb0e81f2 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/classLoader/BytecodeClass.java +++ b/com.ibm.wala.core/src/com/ibm/wala/classLoader/BytecodeClass.java @@ -296,11 +296,16 @@ public abstract class BytecodeClass implements IClass { computeSuperclass(); } if (superClass == null && !getReference().equals(TypeReference.JavaLangObject)) { - throw new IllegalStateException("No superclass found for " + this + " Superclass name " + superName); + throw new NoSuperclassFoundException("No superclass found for " + this + " Superclass name " + superName); } return superClass; } + public TypeName getSuperName() { + return TypeName.findOrCreate(superName); + } + + /* * @see com.ibm.wala.classLoader.IClass#getAllFields() */ diff --git a/com.ibm.wala.core/src/com/ibm/wala/classLoader/NoSuperclassFoundException.java b/com.ibm.wala.core/src/com/ibm/wala/classLoader/NoSuperclassFoundException.java new file mode 100644 index 000000000..8ce870b97 --- /dev/null +++ b/com.ibm.wala.core/src/com/ibm/wala/classLoader/NoSuperclassFoundException.java @@ -0,0 +1,14 @@ +package com.ibm.wala.classLoader; + +/** + * Indicates the superclass for a class was not found in the + * {@link com.ibm.wala.ipa.callgraph.AnalysisScope} + */ +public class NoSuperclassFoundException extends RuntimeException { + + static final long serialVersionUID = 333L; + + public NoSuperclassFoundException(String message) { + super(message); + } +} diff --git a/com.ibm.wala.core/src/com/ibm/wala/classLoader/PhantomClass.java b/com.ibm.wala.core/src/com/ibm/wala/classLoader/PhantomClass.java new file mode 100644 index 000000000..33c4f6c42 --- /dev/null +++ b/com.ibm.wala.core/src/com/ibm/wala/classLoader/PhantomClass.java @@ -0,0 +1,108 @@ +package com.ibm.wala.classLoader; + +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.types.Selector; +import com.ibm.wala.types.TypeReference; +import com.ibm.wala.util.strings.Atom; + +import java.util.Collection; +import java.util.Collections; + +/** + * dummy class representing a missing superclass + */ +public class PhantomClass extends SyntheticClass { + + /** + * @param T type reference describing this class + * @param cha + */ + public PhantomClass(TypeReference T, IClassHierarchy cha) { + super(T, cha); + } + + @Override + public boolean isPublic() { + return false; + } + + @Override + public boolean isPrivate() { + return false; + } + + @Override + public int getModifiers() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Override + public IClass getSuperclass() { + return getClassHierarchy().getRootClass(); + } + + @Override + public Collection getDirectInterfaces() { + return Collections.emptySet(); + } + + @Override + public Collection getAllImplementedInterfaces() { + return Collections.emptySet(); + } + + @Override + public IMethod getMethod(Selector selector) { + throw new UnsupportedOperationException(); + } + + @Override + public IField getField(Atom name) { + throw new UnsupportedOperationException(); + } + + @Override + public IMethod getClassInitializer() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getDeclaredMethods() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getAllInstanceFields() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getAllStaticFields() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getAllFields() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getAllMethods() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getDeclaredInstanceFields() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getDeclaredStaticFields() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isReferenceType() { + return true; + } +} diff --git a/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchy.java b/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchy.java index 9195473ed..67c52ee76 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchy.java +++ b/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchy.java @@ -10,31 +10,23 @@ *******************************************************************************/ 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 java.util.function.Function; - import com.ibm.wala.classLoader.ArrayClass; +import com.ibm.wala.classLoader.BytecodeClass; import com.ibm.wala.classLoader.ClassLoaderFactory; 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.NoSuperclassFoundException; +import com.ibm.wala.classLoader.PhantomClass; 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.TypeName; import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.MonitorUtil.IProgressMonitor; import com.ibm.wala.util.collections.HashMapFactory; @@ -51,6 +43,18 @@ import com.ibm.wala.util.strings.Atom; import com.ibm.wala.util.warnings.Warning; import com.ibm.wala.util.warnings.Warnings; +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 java.util.function.Function; + /** * Simple implementation of a class hierarchy. * @@ -132,6 +136,13 @@ public class ClassHierarchy implements IClassHierarchy { */ private Collection runtimeExceptionTypeRefs; + /** + * when a superclass is missing, should we create a phantom superclass and add the subclass to + * the hierarchy? Note that we can only create phantom superclass when the class is a + * {@link com.ibm.wala.classLoader.BytecodeClass} + */ + private final boolean createPhantomSuperclasses; + /** * Return a set of {@link IClass} that holds all superclasses of klass * @@ -145,44 +156,60 @@ public class ClassHierarchy implements IClassHierarchy { Set 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); - } + try { 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"); + + 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"); + } + } + } + } catch (NoSuperclassFoundException e) { + if (createPhantomSuperclasses && klass instanceof BytecodeClass) { + // create a phantom superclass. add it and the root class to the result + IClass phantom = getPhantomSuperclass((BytecodeClass) klass); + result.add(phantom); + result.add(getRootClass()); + } else { + throw e; } } return result; } - ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, Language language, IProgressMonitor progressMonitor, Map map) + ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, Language language, + IProgressMonitor progressMonitor, Map map, boolean createPhantomSuperclasses) throws ClassHierarchyException, IllegalArgumentException { - this(scope, factory, Collections.singleton(language), progressMonitor, map); + this(scope, factory, Collections.singleton(language), progressMonitor, map, + createPhantomSuperclasses); } - ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, IProgressMonitor progressMonitor, Map map) + ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, IProgressMonitor + progressMonitor, Map map, boolean createPhantomSuperclasses) throws ClassHierarchyException, IllegalArgumentException { - this(scope, factory, scope.getLanguages(), progressMonitor, map); + this(scope, factory, scope.getLanguages(), progressMonitor, map, createPhantomSuperclasses); } ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, Collection languages, - IProgressMonitor progressMonitor, Map map) throws ClassHierarchyException, IllegalArgumentException { + IProgressMonitor progressMonitor, Map map, boolean createPhantomSuperclasses) 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(); this.map = map; + this.createPhantomSuperclasses = createPhantomSuperclasses; if (factory == null) { throw new IllegalArgumentException(); @@ -307,20 +334,26 @@ public class ClassHierarchy implements IClassHierarchy { if (DEBUG) { System.err.println(("Attempt to add class " + klass)); } - Set loadedSuperclasses; + Set loadedSuperclasses = null; 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)); + if (createPhantomSuperclasses && e instanceof NoSuperclassFoundException) { + // this must have been thrown by the getAllImplementedInterfaces() call. + // for now, just pretend it implements no interfaces + loadedSuperInterfaces = Collections.emptySet(); + } else { + // 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; } - Warnings.add(ClassExclusion.create(klass.getReference(), e.getMessage())); - return false; } Node node = findOrCreateNode(klass); @@ -333,8 +366,13 @@ public class ClassHierarchy implements IClassHierarchy { Set workingSuperclasses = HashSetFactory.make(loadedSuperclasses); while (node != null) { IClass c = node.getJavaClass(); - IClass superclass = null; - superclass = c.getSuperclass(); + IClass superclass; + try { + superclass = c.getSuperclass(); + } catch (NoSuperclassFoundException e) { + assert createPhantomSuperclasses; + superclass = getPhantomSuperclass((BytecodeClass) c); + } if (superclass != null) { workingSuperclasses.remove(superclass); Node supernode = findOrCreateNode(superclass); @@ -381,6 +419,18 @@ public class ClassHierarchy implements IClassHierarchy { return true; } + private IClass getPhantomSuperclass(BytecodeClass klass) { + ClassLoaderReference loader = klass.getReference().getClassLoader(); + TypeName superName = klass.getSuperName(); + TypeReference superRef = TypeReference.findOrCreate(loader, superName); + IClass superClass = lookupClass(superRef); + if (superClass == null) { + superClass = new PhantomClass(superRef, this); + addClass(superClass); + } + return superClass; + } + /** * Record that a klass implements a particular interface */ diff --git a/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchyFactory.java b/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchyFactory.java index 2c0fb088f..7376a4bb0 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchyFactory.java +++ b/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/ClassHierarchyFactory.java @@ -10,17 +10,15 @@ *******************************************************************************/ package com.ibm.wala.ipa.cha; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - import com.ibm.wala.classLoader.ClassLoaderFactory; import com.ibm.wala.classLoader.ClassLoaderFactoryImpl; import com.ibm.wala.classLoader.Language; import com.ibm.wala.ipa.callgraph.AnalysisScope; -import com.ibm.wala.ipa.cha.ClassHierarchy.Node; -import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.MonitorUtil.IProgressMonitor; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + public class ClassHierarchyFactory { /** @@ -34,6 +32,19 @@ public class ClassHierarchyFactory { return make(scope, new ClassLoaderFactoryImpl(scope.getExclusions())); } + /** + * @return a ClassHierarchy object representing the analysis scope, where phantom classes are + * created when superclasses are missing + * + * @throws ClassHierarchyException + */ + public static ClassHierarchy makeWithPhantom(AnalysisScope scope) throws ClassHierarchyException { + if (scope == null) { + throw new IllegalArgumentException("null scope"); + } + return makeWithPhantom(scope, new ClassLoaderFactoryImpl(scope.getExclusions())); + } + /** * temporarily marking this internal to avoid infinite sleep with randomly chosen IProgressMonitor. */ @@ -45,31 +56,41 @@ public class ClassHierarchyFactory { } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory) throws ClassHierarchyException { + return make(scope, factory, false); + } + + private static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, boolean + createPhantomSuperclasses) throws ClassHierarchyException { if (scope == null) { throw new IllegalArgumentException("null scope"); } if (factory == null) { throw new IllegalArgumentException("null factory"); } - return new ClassHierarchy(scope, factory, null, new ConcurrentHashMap()); + return new ClassHierarchy(scope, factory, null, new ConcurrentHashMap<>(), + createPhantomSuperclasses); } + public static ClassHierarchy makeWithPhantom(AnalysisScope scope, ClassLoaderFactory factory) + throws ClassHierarchyException { + return make(scope, factory, true); + } /** * 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, new ConcurrentHashMap()); + return new ClassHierarchy(scope, factory, monitor, new ConcurrentHashMap<>(), false); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Set languages) throws ClassHierarchyException { - return new ClassHierarchy(scope, factory, languages, null, new ConcurrentHashMap()); + return new ClassHierarchy(scope, factory, languages, null, new ConcurrentHashMap<>(), false); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Language language) throws ClassHierarchyException { - return new ClassHierarchy(scope, factory, language, null, new ConcurrentHashMap()); + return new ClassHierarchy(scope, factory, language, null, new ConcurrentHashMap<>(), false); } /** @@ -80,7 +101,7 @@ public class ClassHierarchyFactory { if (factory == null) { throw new IllegalArgumentException("null factory"); } - return new ClassHierarchy(scope, factory, language, monitor, new ConcurrentHashMap()); + return new ClassHierarchy(scope, factory, language, monitor, new ConcurrentHashMap<>(), false); } } diff --git a/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/SeqClassHierarchyFactory.java b/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/SeqClassHierarchyFactory.java index 5e97f10ed..9fa29d436 100644 --- a/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/SeqClassHierarchyFactory.java +++ b/com.ibm.wala.core/src/com/ibm/wala/ipa/cha/SeqClassHierarchyFactory.java @@ -10,17 +10,15 @@ *******************************************************************************/ package com.ibm.wala.ipa.cha; -import java.util.Set; - import com.ibm.wala.classLoader.ClassLoaderFactory; import com.ibm.wala.classLoader.ClassLoaderFactoryImpl; import com.ibm.wala.classLoader.Language; import com.ibm.wala.ipa.callgraph.AnalysisScope; -import com.ibm.wala.ipa.cha.ClassHierarchy.Node; -import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.MonitorUtil.IProgressMonitor; import com.ibm.wala.util.collections.HashMapFactory; +import java.util.Set; + public class SeqClassHierarchyFactory { /** @@ -51,7 +49,8 @@ public class SeqClassHierarchyFactory { if (factory == null) { throw new IllegalArgumentException("null factory"); } - return new ClassHierarchy(scope, factory, null, HashMapFactory.make()); + return new ClassHierarchy(scope, factory, null, HashMapFactory.make(), + false); } /** @@ -59,17 +58,17 @@ public class SeqClassHierarchyFactory { */ public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, IProgressMonitor monitor) throws ClassHierarchyException { - return new ClassHierarchy(scope, factory, monitor, HashMapFactory.make()); + return new ClassHierarchy(scope, factory, monitor, HashMapFactory.make(), false); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Set languages) throws ClassHierarchyException { - return new ClassHierarchy(scope, factory, languages, null, HashMapFactory.make()); + return new ClassHierarchy(scope, factory, languages, null, HashMapFactory.make(), false); } public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Language language) throws ClassHierarchyException { - return new ClassHierarchy(scope, factory, language, null, HashMapFactory.make()); + return new ClassHierarchy(scope, factory, language, null, HashMapFactory.make(), false); } /** @@ -80,7 +79,7 @@ public class SeqClassHierarchyFactory { if (factory == null) { throw new IllegalArgumentException("null factory"); } - return new ClassHierarchy(scope, factory, language, monitor, HashMapFactory.make()); + return new ClassHierarchy(scope, factory, language, monitor, HashMapFactory.make(), false); } }