/*******************************************************************************
* 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.shrikeBT.analysis;
import java.util.HashSet;
import java.util.Iterator;
import com.ibm.wala.shrikeBT.Constants;
/**
* This class takes the raw information from a ClassHierarchyProvider and computes type operations (subtype check, type union). All
* operations are static.
*
* Because ClassHierarchyProvider sometimes only provides partial information, these routines sometimes answer "don't know".
*/
public final class ClassHierarchy {
private ClassHierarchy() {
}
/**
* Equals Constants.NO
*/
public static final int NO = Constants.NO;
/**
* Equals Constants.YES
*/
public static final int YES = Constants.YES;
/**
* Equals Constants.MAYBE
*/
public static final int MAYBE = Constants.MAYBE;
private static int checkSuperinterfacesContain(ClassHierarchyProvider hierarchy, String t1, String t2, HashSet<String> visited) {
String[] ifaces = hierarchy.getSuperInterfaces(t1);
if (ifaces == null) {
return MAYBE;
}
int r = NO;
for (int i = 0; i < ifaces.length; i++) {
String iface = ifaces[i];
if (!visited.contains(iface)) {
visited.add(iface);
if (iface.equals(t2)) {
return YES;
} else {
int v = checkSuperinterfacesContain(hierarchy, iface, t2, visited);
if (v == YES) {
return YES;
} else if (v == MAYBE) {
r = MAYBE;
}
}
}
}
return r;
}
private static int checkSupertypesContain(ClassHierarchyProvider hierarchy, String t1, String t2) {
int r = NO;
String c = t1;
while (true) {
String sup = hierarchy.getSuperClass(c);
if (sup == null) {
if (!c.equals(Constants.TYPE_Object)) {
r = MAYBE;
}
break;
}
if (sup.equals(t2)) {
return YES;
}
c = sup;
}
if (hierarchy.isInterface(t2) != NO) {
HashSet<String> visited = new HashSet<String>();
for (c = t1; c != null; c = hierarchy.getSuperClass(c)) {
int v = checkSuperinterfacesContain(hierarchy, c, t2, visited);
if (v == YES) {
return YES;
} else if (v == MAYBE) {
r = MAYBE;
}
}
}
return r;
}
private static int checkSubtypesContain(ClassHierarchyProvider hierarchy, String t1, String t2, HashSet<String> visited) {
// No interface is a subclass of a real class
if (hierarchy.isInterface(t1) == NO && hierarchy.isInterface(t2) == YES) {
return NO;
}
String[] subtypes = hierarchy.getSubClasses(t1);
if (subtypes == null) {
return MAYBE;
}
int r = NO;
for (int i = 0; i < subtypes.length; i++) {
String subt = subtypes[i];
if (!visited.contains(subt)) {
visited.add(subt);
if (subt.equals(t2)) {
return YES;
} else {
int v = checkSubtypesContain(hierarchy, subt, t2, visited);
if (v == YES) {
return YES;
} else if (v == MAYBE) {
r = MAYBE;
}
}
}
}
return r;
}
private static int checkSubtypeOfHierarchy(ClassHierarchyProvider hierarchy, String t1, String t2) {
if (t2.equals(Constants.TYPE_Object)) {
return YES;
} else {
int v = checkSupertypesContain(hierarchy, t1, t2);
if (v == MAYBE) {
v = checkSubtypesContain(hierarchy, t2, t1, new HashSet<String>());
}
return v;
}
}
/**
* Perform subtype check.
*
* @param hierarchy the hierarchy information to use for the decision
* @param t1 a type in JVM format
* @param t2 a type in JVM format
* @return whether t1 is a subtype of t2 (YES, NO, MAYBE)
*/
public static int isSubtypeOf(ClassHierarchyProvider hierarchy, String t1, String t2) {
if (t1 == null || t2 == null) {
return NO;
} else if (t1.equals(t2)) {
return YES;
} else if (t1.equals(Constants.TYPE_unknown) || t2.equals(Constants.TYPE_unknown)) {
return MAYBE;
} else {
switch (t1.charAt(0)) {
case 'L':
if (t1.equals(Constants.TYPE_null)) {
return YES;
} else if (t2.startsWith("[")) {
return NO;
} else if (hierarchy == null) {
return MAYBE;
} else {
return checkSubtypeOfHierarchy(hierarchy, t1, t2);
}
case '[':
if (t2.equals(Constants.TYPE_Object) || t2.equals("Ljava/io/Serializable;") || t2.equals("Ljava/lang/Cloneable;")) {
return YES;
} else if (t2.startsWith("[")) {
return isSubtypeOf(hierarchy, t1.substring(1), t2.substring(1));
} else {
return NO;
}
default:
return NO;
}
}
}
private static boolean insertSuperInterfaces(ClassHierarchyProvider hierarchy, String t, HashSet<String> supers) {
String[] ifaces = hierarchy.getSuperInterfaces(t);
if (ifaces == null) {
return false;
} else {
boolean r = true;
for (int i = 0; i < ifaces.length; i++) {
String iface = ifaces[i];
if (!supers.contains(iface)) {
supers.add(iface);
if (!insertSuperInterfaces(hierarchy, ifaces[i], supers)) {
r = false;
}
}
}
return r;
}
}
private static boolean insertSuperClasses(ClassHierarchyProvider hierarchy, String t, HashSet<String> supers) {
String last = t;
for (String c = t; c != null; c = hierarchy.getSuperClass(c)) {
supers.add(c);
last = c;
}
return last.equals(Constants.TYPE_Object);
}
private static boolean insertSuperClassInterfaces(ClassHierarchyProvider hierarchy, String t, HashSet<String> supers) {
boolean r = true;
for (String c = t; c != null; c = hierarchy.getSuperClass(c)) {
if (!insertSuperInterfaces(hierarchy, c, supers)) {
r = false;
}
}
return r;
}
private static boolean collectDominatingSuperClasses(ClassHierarchyProvider hierarchy, String t, HashSet<String> matches,
HashSet<String> supers) {
String last = t;
for (String c = t; c != null; c = hierarchy.getSuperClass(c)) {
if (matches.contains(c)) {
supers.add(c);
return true;
}
last = c;
}
return last.equals(Constants.TYPE_Object);
}
private static boolean collectDominatingSuperInterfacesFromClass(ClassHierarchyProvider hierarchy, String t,
HashSet<String> matches, HashSet<String> supers) {
String[] ifaces = hierarchy.getSuperInterfaces(t);
if (ifaces == null) {
return false;
} else {
boolean r = true;
for (int i = 0; i < ifaces.length; i++) {
String iface = ifaces[i];
if (!supers.contains(iface)) {
supers.add(iface);
if (!insertSuperInterfaces(hierarchy, ifaces[i], supers)) {
r = false;
}
}
}
return r;
}
}
private static boolean collectDominatingSuperInterfaces(ClassHierarchyProvider hierarchy, String t, HashSet<String> matches,
HashSet<String> supers) {
boolean r = true;
for (String c = t; c != null && !supers.contains(c); c = hierarchy.getSuperClass(c)) {
if (!collectDominatingSuperInterfacesFromClass(hierarchy, c, matches, supers)) {
r = false;
}
}
return r;
}
private static String findCommonSupertypeHierarchy(ClassHierarchyProvider hierarchy, String t1, String t2) {
if (isSubtypeOf(hierarchy, t1, t2) == YES) {
return t2;
} else if (isSubtypeOf(hierarchy, t2, t1) == YES) {
return t1;
}
HashSet<String> t1Supers = new HashSet<String>();
t1Supers.add(Constants.TYPE_Object);
boolean t1ExactClasses = insertSuperClasses(hierarchy, t1, t1Supers);
int t1ClassCount = t1Supers.size();
boolean t1ExactInterfaces = insertSuperClassInterfaces(hierarchy, t1, t1Supers);
HashSet<String> t2Supers = new HashSet<String>();
boolean t2ExactClasses = collectDominatingSuperClasses(hierarchy, t2, t1Supers, t2Supers);
if (t2Supers.size() == 0) {
// we didn't find a common superclass, so we don't know what it might be
return Constants.TYPE_unknown;
} // otherwise we know for sure what the common superclass is
boolean t2ExactInterfaces;
if (t1ExactInterfaces && t1ExactClasses && t1Supers.size() - t1ClassCount == 0) {
// t1 doesn't have any interfaces so we don't need to search t2's
// interfaces
t2ExactInterfaces = true;
} else {
t2ExactInterfaces = collectDominatingSuperInterfaces(hierarchy, t2, t1Supers, t2Supers);
if (!t1ExactInterfaces && t2Supers.size() != 1) {
// we found an interface; it might also apply to t1; must bail
return Constants.TYPE_unknown;
}
}
if (!t2ExactClasses || !t2ExactInterfaces) {
// there may be some common interfaces that we don't know about
return "";
}
for (Iterator<String> iter = t2Supers.iterator(); iter.hasNext();) {
String element = iter.next();
boolean subsumed = false;
for (Iterator<String> iterator = t2Supers.iterator(); iterator.hasNext();) {
String element2 = iterator.next();
if (element != element2 && isSubtypeOf(hierarchy, element2, element) == YES) {
subsumed = true;
break;
}
}
if (subsumed) {
iter.remove();
}
}
if (t2Supers.size() == 1) {
return t2Supers.iterator().next();
} else if (t2Supers.size() == 0) {
return Constants.TYPE_Object;
} else {
return Constants.TYPE_unknown; // some non-representable combination of
// class and interfaces
}
}
/**
* Compute the most specific common supertype.
*
* @param hierarchy the hierarchy information to use for the decision
* @param t1 a type in JVM format
* @param t2 a type in JVM format
* @return the most specific common supertype of t1 and t2, or TYPE_unknown if it cannot be determined or cannot be represented as
* a Java type, or null if there is no common supertype
*/
public static String findCommonSupertype(ClassHierarchyProvider hierarchy, String t1, String t2) {
if (t1 == null || t2 == null) {
return null;
} else if (t1.equals(t2)) {
return t1;
} else if (t1.equals(Constants.TYPE_unknown) || t2.equals(Constants.TYPE_unknown)) {
return Constants.TYPE_unknown;
} else {
if (t2.charAt(0) == '[') { // put array into t1
String t = t1;
t1 = t2;
t2 = t;
}
switch (t1.charAt(0)) {
case 'L':
// two non-array types
// if either one is constant null, return the other one
if (t1.equals(Constants.TYPE_null)) {
return t2;
} else if (t2.equals(Constants.TYPE_null)) {
return t1;
} else if (hierarchy == null) {
// don't have a class hierarchy
return Constants.TYPE_unknown;
} else {
return findCommonSupertypeHierarchy(hierarchy, t1, t2);
}
case '[': {
char ch2 = t2.charAt(0);
if (ch2 == '[') {
char ch1_1 = t1.charAt(1);
if (ch1_1 == '[' || ch1_1 == 'L') {
return "[" + findCommonSupertype(hierarchy, t1.substring(1), t2.substring(1));
} else {
return Constants.TYPE_Object;
}
} else if (ch2 == 'L') {
if (t2.equals(Constants.TYPE_null)) {
return t1;
} else if (t2.equals("Ljava/io/Serializable;") || t2.equals("Ljava/lang/Cloneable;")) {
return t2;
} else {
return Constants.TYPE_Object;
}
} else {
return null;
}
}
default:
return null;
}
}
}
}