/******************************************************************************* * 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; } } } }