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.
This commit is contained in:
Manu Sridharan 2018-07-19 16:10:35 +02:00 committed by GitHub
parent 5edf49254c
commit aeb17dfca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 323 additions and 60 deletions

View File

@ -0,0 +1 @@
!/*.class

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2008 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.core.tests.cha;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.core.tests.util.TestConstants;
import com.ibm.wala.core.tests.util.WalaTestCase;
import com.ibm.wala.ipa.callgraph.AnalysisCacheImpl;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ipa.cha.ClassHierarchy;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.config.AnalysisScopeReader;
import com.ibm.wala.util.io.FileProvider;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.Collection;
public class MissingSuperTest extends WalaTestCase {
/**
* Test handling of an invalid class where a non-abstract method has no code.
* We want to throw an exception rather than crash.
*
* @throws IOException
* @throws ClassHierarchyException
*/
@Test
public void testMissingSuper() throws IOException, ClassHierarchyException {
AnalysisScope scope = AnalysisScopeReader.readJavaScope(TestConstants.WALA_TESTDATA,
(new FileProvider()).getFile("J2SEClassHierarchyExclusions.txt"), DupFieldsTest.class.getClassLoader());
TypeReference ref = TypeReference.findOrCreate(ClassLoaderReference.Application,
"Lmissingsuper/MissingSuper");
// without phantom classes, won't be able to resolve
ClassHierarchy cha = ClassHierarchyFactory.make(scope);
Assert.assertNull("lookup should not work", cha.lookupClass(ref));
// with phantom classes, lookup and IR construction should work
cha = ClassHierarchyFactory.makeWithPhantom(scope);
IClass klass = cha.lookupClass(ref);
Assert.assertNotNull("expected class MissingSuper to load", klass);
IAnalysisCacheView cache = new AnalysisCacheImpl();
Collection<? extends IMethod> declaredMethods = klass.getDeclaredMethods();
Assert.assertEquals(declaredMethods.toString(), 2, declaredMethods.size());
for (IMethod m : declaredMethods) {
// should succeed
cache.getIR(m);
}
}
}

View File

@ -296,11 +296,16 @@ public abstract class BytecodeClass<T extends IClassLoader> 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()
*/

View File

@ -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);
}
}

View File

@ -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<? extends IClass> getDirectInterfaces() {
return Collections.emptySet();
}
@Override
public Collection<IClass> 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<? extends IMethod> getDeclaredMethods() {
throw new UnsupportedOperationException();
}
@Override
public Collection<IField> getAllInstanceFields() {
throw new UnsupportedOperationException();
}
@Override
public Collection<IField> getAllStaticFields() {
throw new UnsupportedOperationException();
}
@Override
public Collection<IField> getAllFields() {
throw new UnsupportedOperationException();
}
@Override
public Collection<? extends IMethod> getAllMethods() {
throw new UnsupportedOperationException();
}
@Override
public Collection<IField> getDeclaredInstanceFields() {
throw new UnsupportedOperationException();
}
@Override
public Collection<IField> getDeclaredStaticFields() {
throw new UnsupportedOperationException();
}
@Override
public boolean isReferenceType() {
return true;
}
}

View File

@ -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<TypeReference> 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<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);
}
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<TypeReference, Node> map)
ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, Language language,
IProgressMonitor progressMonitor, Map<TypeReference, Node> 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<TypeReference, Node> map)
ClassHierarchy(AnalysisScope scope, ClassLoaderFactory factory, IProgressMonitor
progressMonitor, Map<TypeReference, Node> 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<Language> languages,
IProgressMonitor progressMonitor, Map<TypeReference, Node> map) throws ClassHierarchyException, IllegalArgumentException {
IProgressMonitor progressMonitor, Map<TypeReference, Node> 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<IClass> loadedSuperclasses;
Set<IClass> loadedSuperclasses = null;
Collection<IClass> 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
*/

View File

@ -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<TypeReference, Node>());
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<TypeReference, Node>());
return new ClassHierarchy(scope, factory, monitor, new ConcurrentHashMap<>(), false);
}
public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Set<Language> languages)
throws ClassHierarchyException {
return new ClassHierarchy(scope, factory, languages, null, new ConcurrentHashMap<TypeReference, Node>());
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<TypeReference, Node>());
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<TypeReference, Node>());
return new ClassHierarchy(scope, factory, language, monitor, new ConcurrentHashMap<>(), false);
}
}

View File

@ -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.<TypeReference, Node>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.<TypeReference, Node>make());
return new ClassHierarchy(scope, factory, monitor, HashMapFactory.make(), false);
}
public static ClassHierarchy make(AnalysisScope scope, ClassLoaderFactory factory, Set<Language> languages)
throws ClassHierarchyException {
return new ClassHierarchy(scope, factory, languages, null, HashMapFactory.<TypeReference, Node>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.<TypeReference, Node>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.<TypeReference, Node>make());
return new ClassHierarchy(scope, factory, language, monitor, HashMapFactory.make(), false);
}
}