Mirror of the BLOAT repository https://www.cs.purdue.edu/homes/hosking/bloat/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
bloat/src/EDU/purdue/cs/bloat/inline/CallGraph.java

763 lines
22 KiB

/**
* All files in the distribution of BLOAT (Bytecode Level Optimization and
* Analysis tool for Java(tm)) are Copyright 1997-2001 by the Purdue
* Research Foundation of Purdue University. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package EDU.purdue.cs.bloat.inline;
import java.io.*;
import java.util.*;
import EDU.purdue.cs.bloat.editor.*;
import EDU.purdue.cs.bloat.reflect.*;
import EDU.purdue.cs.bloat.util.*;
/**
* Grants access to certain information about a Java program. At least one root
* method must be specified. From these root methods, the call graph and
* information such as the classes that are instantiated during the Java program
* is computed.
*
* <p>
*
* The construction of the call graph is in the spirit of the "Program
* Virtual-call Graph" presented in [Bacon97]. However, certain changes have
* been made to tailor it to BLOAT and Java and to make the overall
* representation smaller.
*
* <p>
*
* Rapid type analysis is integrated into the construction of the call graph. A
* virtual method is not examined until we know that its declaring class has
* been instantiated.
*
* <p>
*
* Some classes are created internally by the VM and are missed by our analysis.
* So, we maintain a set of "pre-live" classes. We consider all of their
* constructors to be live.
*/
public class CallGraph {
public static boolean DEBUG = false;
private static Set preLive; // "Pre-live" classes
public static boolean USEPRELIVE = true;
public static boolean USE1_2 = true;
private Set roots; // Root methods (MethodRefs)
private Map calls; // Maps methods to the methods they
// call (virtual calls are not resolved)
private Set liveClasses; // Classes (Types) that have been instantiated
private Map resolvesTo; // Maps methods to the methods they resolve to
private Map blocked; // Maps types to methods blocked on those types
List worklist; // Methods to process
Set liveMethods; // Methods that may be executed
InlineContext context;
private ClassHierarchy hier;
static void db(final String s) {
if (CallGraph.DEBUG) {
System.out.println(s);
}
}
/**
* Initialize the set of classes that are "pre-live"
*/
private static void init() {
// We can't do this in the static initializer because USE1_2 might
// not have the desired value.
CallGraph.preLive = new HashSet();
CallGraph.preLive.add("java.lang.Boolean");
CallGraph.preLive.add("java.lang.Class");
CallGraph.preLive.add("java.lang.ClassLoader");
CallGraph.preLive.add("java.lang.Compiler");
CallGraph.preLive.add("java.lang.Integer");
CallGraph.preLive.add("java.lang.SecurityManager");
CallGraph.preLive.add("java.lang.String");
CallGraph.preLive.add("java.lang.StringBuffer");
CallGraph.preLive.add("java.lang.System");
CallGraph.preLive.add("java.lang.StackOverflowError");
CallGraph.preLive.add("java.lang.Thread");
CallGraph.preLive.add("java.lang.ThreadGroup");
CallGraph.preLive.add("java.io.BufferedInputStream");
CallGraph.preLive.add("java.io.BufferedReader");
CallGraph.preLive.add("java.io.BufferedOutputStream");
CallGraph.preLive.add("java.io.BufferedWriter");
CallGraph.preLive.add("java.io.File");
CallGraph.preLive.add("java.io.FileDescriptor");
CallGraph.preLive.add("java.io.InputStreamReader");
CallGraph.preLive.add("java.io.ObjectStreamClass");
CallGraph.preLive.add("java.io.OutputStreamWriter");
CallGraph.preLive.add("java.io.PrintStream");
CallGraph.preLive.add("java.io.PrintWriter");
CallGraph.preLive.add("java.net.URL");
CallGraph.preLive.add("java.security.Provider");
CallGraph.preLive.add("java.security.Security");
CallGraph.preLive.add("java.util.Hashtable");
CallGraph.preLive.add("java.util.ListResourceBundle");
CallGraph.preLive.add("java.util.Locale");
CallGraph.preLive.add("java.util.Properties");
CallGraph.preLive.add("java.util.Stack");
CallGraph.preLive.add("java.util.Vector");
CallGraph.preLive.add("java.util.zip.ZipFile");
// Some pre-live classes are only available on JDK1.2.
if (CallGraph.USE1_2) {
CallGraph.preLive.add("java.lang.Package");
CallGraph.preLive.add("java.lang.ref.Finalizer");
CallGraph.preLive.add("java.lang.ref.ReferenceQueue");
CallGraph.preLive.add("java.io.FilePermission");
CallGraph.preLive.add("java.io.UnixFileSystem");
CallGraph.preLive.add("java.net.URLClassLoader");
CallGraph.preLive.add("java.security.SecureClassLoader");
CallGraph.preLive.add("java.security.AccessController");
CallGraph.preLive.add("java.text.resources.LocaleElements");
CallGraph.preLive.add("java.text.resources.LocaleElements_en");
CallGraph.preLive.add("java.util.HashMap");
CallGraph.preLive.add("java.util.jar.JarFile");
}
}
/**
* Adds (the name of) a class to the set of classes that are considered to
* be "pre-live"
*/
public static void addPreLive(final String name) {
if (CallGraph.preLive == null) {
CallGraph.init();
}
CallGraph.preLive.add(name);
}
/**
* Removes a class from the set of "pre-live" classes
*
* @return <tt>true</tt> if the class was "pre-live"
*/
public static boolean removePreLive(final String name) {
if (CallGraph.preLive == null) {
CallGraph.init();
}
return (CallGraph.preLive.remove(name));
}
/**
* Constructor.
*
* @param context
* <Tt>InlineContext</tt> used to examine classes and methods.
*
* @param roots
* The methods (represented as <tt>MemberRef</tt>s) considered
* to the roots (that is, the "main" methods) of the call graph.
* Presumably, only static methods or constructors can be root
* methods.
*/
public CallGraph(final InlineContext context, final Set roots) {
Assert.isTrue(roots != null, "A call graph must have roots");
Assert.isTrue(roots.size() > 0, "A call graph must have roots");
if (CallGraph.preLive == null) {
CallGraph.init();
}
this.context = context;
this.hier = context.getHierarchy();
this.roots = roots;
this.liveClasses = new HashSet();
this.resolvesTo = new HashMap();
this.calls = new HashMap();
this.blocked = new HashMap();
this.worklist = new LinkedList(this.roots);
this.liveMethods = new HashSet();
// To save space, make one InstructionVisitor and use it on every
// Instruction.
final CallVisitor visitor = new CallVisitor(this);
CallGraph.db("Adding pre-live classes");
doPreLive();
CallGraph.db("Constructing call graph");
// Examine each method in the worklist. At each constructor
// invocation make note of the type that was created. At each
// method call determine all possible methods that it can resolve
// to. Add the methods of classes that have been instantiated to
// the worklist.
while (!worklist.isEmpty()) {
final MemberRef caller = (MemberRef) worklist.remove(0);
if (liveMethods.contains(caller)) {
// We've already handled this method
continue;
}
MethodEditor callerMethod = null;
try {
callerMethod = context.editMethod(caller);
} catch (final NoSuchMethodException ex1) {
System.err.println("** Could not find method: " + caller);
ex1.printStackTrace(System.err);
System.exit(1);
}
// If the method is abstract or native, we can't do anything
// with it.
if (callerMethod.isAbstract()) {
continue;
}
liveMethods.add(caller);
if (callerMethod.isNative()) {
// We still want native methods to be live
continue;
}
CallGraph.db("\n Examining method " + caller);
final Set callees = new HashSet(); // Methods called by caller
calls.put(caller, callees);
// If the method is static or is a constructor, the classes
// static initializer method must have been called. Make note
// of this.
if (callerMethod.isStatic() || callerMethod.isConstructor()) {
addClinit(callerMethod.declaringClass().type());
}
// Examine the instructions in the caller method.
final Iterator code = callerMethod.code().iterator();
visitor.setCaller(callerMethod);
while (code.hasNext()) {
final Object o = code.next();
if (o instanceof Instruction) {
final Instruction inst = (Instruction) o;
inst.visit(visitor);
}
}
}
// We're done constructing the call graph. Try to free up some
// memory.
blocked = null;
}
/**
* Helper method to add the static initializers and all constructors of the
* pre-live classes to the worklist, etc.
*/
private void doPreLive() {
if (!CallGraph.USEPRELIVE) {
return;
}
CallGraph.db("Making constructors of pre-live classes live");
final Iterator iter = CallGraph.preLive.iterator();
while (iter.hasNext()) {
String name = (String) iter.next();
CallGraph.db(" " + name + " is pre-live");
name = name.replace('.', '/');
ClassEditor ce = null;
try {
ce = context.editClass(name);
} catch (final ClassNotFoundException ex1) {
System.err.println("** Cannot find pre-live class: " + name);
ex1.printStackTrace(System.err);
System.exit(1);
}
// Make class and static initializer live
liveClasses.add(ce.type());
addClinit(ce.type());
// Make all constructors live
final MethodInfo[] methods = ce.methods();
for (int i = 0; i < methods.length; i++) {
final MethodEditor method = context.editMethod(methods[i]);
if (method.name().equals("<init>")) {
CallGraph.db(" " + method);
worklist.add(method.memberRef());
}
}
}
}
/**
* Adds the static initializer for a given <tt>Type</tt> to the worklist.
*/
void addClinit(final Type type) {
try {
final ClassEditor ce = context.editClass(type);
final MethodInfo[] methods = ce.methods();
for (int i = 0; i < methods.length; i++) {
final MethodEditor clinit = context.editMethod(methods[i]);
if (clinit.name().equals("<clinit>")) {
worklist.add(clinit.memberRef());
context.release(clinit.methodInfo());
break;
}
context.release(clinit.methodInfo());
}
context.release(ce.classInfo());
} catch (final ClassNotFoundException ex1) {
System.err.println("** Could not find class for " + type);
ex1.printStackTrace(System.err);
System.exit(1);
}
}
/**
* Handles a virtual call. Determines all possible methods the call could
* resolve to. Adds the method whose declaring classes are live to the
* worklist. Blocks the rest on their declaring types.
*/
void doVirtual(final MethodEditor caller, final MemberRef callee) {
// Figure out which methods the callee can resolve to.
final Iterator resolvesToWith = hier.resolvesToWith(callee).iterator();
while (resolvesToWith.hasNext()) {
final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) resolvesToWith
.next();
CallGraph.db(" resolves to " + rtw.method);
// Add all possible non-abstract methods to the call graph.
// This way, when a blocked method becomes unblocked, it will
// still be in the call graph.
addCall(caller, rtw.method);
Iterator rTypes = rtw.rTypes.iterator();
boolean isLive = false; // Is one of the rTypes live?
while (rTypes.hasNext()) {
final Type rType = (Type) rTypes.next();
if (liveClasses.contains(rType)) {
isLive = true;
CallGraph.db(" Method " + rtw.method + " is live");
worklist.add(rtw.method);
break;
}
}
if (!isLive) {
// If none of the receiver types is live, then the method is
// blocked on all possible receiver types.
rTypes = rtw.rTypes.iterator();
final StringBuffer sb = new StringBuffer();
while (rTypes.hasNext()) {
final Type rType = (Type) rTypes.next();
Set blockedMethods = (Set) blocked.get(rType);
if (blockedMethods == null) {
blockedMethods = new HashSet();
blocked.put(rType, blockedMethods);
}
blockedMethods.add(rtw.method);
sb.append(rType.toString());
if (rTypes.hasNext()) {
sb.append(',');
}
}
CallGraph.db(" Blocked " + rtw.method + " on " + sb);
}
}
}
/**
* Makes note of one method calling another. This does not make the method
* live.
*/
void addCall(final MethodEditor callerMethod, final MemberRef callee) {
// Just maintain the calls mapping
final MemberRef caller = callerMethod.memberRef();
Set callees = (Set) this.calls.get(caller);
if (callees == null) {
callees = new HashSet();
this.calls.put(caller, callees);
}
callees.add(callee);
}
/**
* Marks a <tt>Type</tt> as being lives. It also unblocks any methods that
* were blocked on the type.
*/
void makeLive(final Type type) {
if (this.liveClasses.contains(type)) {
return;
}
// Make type live and unblock all methods blocked on it
CallGraph.db(" Making " + type + " live");
liveClasses.add(type);
final Set blockedMethods = (Set) blocked.remove(type);
if (blockedMethods != null) {
final Iterator iter = blockedMethods.iterator();
while (iter.hasNext()) {
final MemberRef method = (MemberRef) iter.next();
CallGraph.db(" Unblocking " + method);
worklist.add(method);
}
}
}
/**
* Returns the methods (<tt>MemberRef</tt>s) to which a given method
* could resolve. Only live methods are taken into account. The methods are
* sorted such that overriding methods appear before overriden methods.
*/
public Set resolvesTo(final MemberRef method) {
TreeSet resolvesTo = (TreeSet) this.resolvesTo.get(method);
if (resolvesTo == null) {
resolvesTo = new TreeSet(new MemberRefComparator(context));
this.resolvesTo.put(method, resolvesTo);
final Set liveMethods = this.liveMethods();
final Iterator rtws = hier.resolvesToWith(method).iterator();
while (rtws.hasNext()) {
final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) rtws
.next();
if (liveMethods.contains(rtw.method)) {
resolvesTo.add(rtw.method);
}
}
}
// Return a clone so that the set may safely be modified
return ((Set) resolvesTo.clone());
}
/**
* Returns the methods (<tt>MemberRef</tt>s) to which a given method
* could resolve given that the receiver is in a certain set of types. Only
* live methods are taken into account. The methods are sorted such that
* overriding methods appear before overriden methods.
*/
public Set resolvesTo(final MemberRef method, final Set rTypes) {
if (rTypes.isEmpty()) {
return (resolvesTo(method));
}
// Since we're only dealing with a subset of types, don't bother
// with the caching stuff.
final TreeSet resolvesTo = new TreeSet(new MemberRefComparator(context));
final Set liveMethods = this.liveMethods();
final Iterator rtws = hier.resolvesToWith(method).iterator();
while (rtws.hasNext()) {
final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) rtws
.next();
if (liveMethods.contains(rtw.method)) {
final HashSet clone = (HashSet) rtw.rTypes.clone();
clone.retainAll(rTypes);
if (!clone.isEmpty()) {
// Only keep method that have at least one possible
// receiver type in rTypes
resolvesTo.add(rtw.method);
}
}
}
// Return a clone so that the set may safely be modified
return ((Set) resolvesTo.clone());
}
/**
* Returns the set of methods (<tt>MemberRef</tt>s) that the
* construction algorithm has deemed to be live.
*/
public Set liveMethods() {
// Not all of the methods in the calls mapping are necessarily
// live. So, we have to maintain a separate set.
return (this.liveMethods);
}
/**
* Returns the root methods (<tt>MemberRef</tt>s) of the call graph.
*/
public Set roots() {
return (this.roots);
}
/**
* Returns the set of classes (<tt>Type</tt>s) that are instantiated in
* the program.
*/
public Set liveClasses() {
return (this.liveClasses);
}
/**
* Prints a textual prepresentation of the <tt>CallGraph</tt> to a
* <tt>PrintWriter</tt>.
*
* @param out
* To where we print
* @param printLeaves
* If <tt>true</tt>, leaf methods (methods that do not call
* any other methods) are printed
*/
public void print(final PrintWriter out, boolean printLeaves) {
final Iterator callers = calls.keySet().iterator();
while (callers.hasNext()) {
final MemberRef caller = (MemberRef) callers.next();
final Iterator callees = ((Set) calls.get(caller)).iterator();
if (!printLeaves && !callees.hasNext()) {
continue;
}
out.print(caller.declaringClass() + "." + caller.name()
+ caller.type());
if (roots.contains(caller)) {
out.print(" (root)");
}
out.println("");
while (callees.hasNext()) {
final MemberRef callee = (MemberRef) callees.next();
// Only print live methods
if (!calls.containsKey(callee)) {
continue;
}
out.println(" " + callee.declaringClass() + "."
+ callee.name() + callee.type());
}
out.println("");
}
}
/**
* Prints a summary of the call graph. Including the classes that are live
* and which methods are blocked.
*/
public void printSummary(final PrintWriter out) {
out.println("Instantiated classes:");
final Iterator instantiated = this.liveClasses.iterator();
while (instantiated.hasNext()) {
final Type type = (Type) instantiated.next();
out.println(" " + type.toString());
}
out.println("\nBlocked methods:");
if (blocked != null) {
final Iterator types = blocked.keySet().iterator();
while (types.hasNext()) {
final Type type = (Type) types.next();
out.println(" " + type);
final Set set = (Set) blocked.get(type);
if (set != null) {
final Iterator methods = set.iterator();
while (methods.hasNext()) {
final MemberRef method = (MemberRef) methods.next();
out.println(" " + method);
}
}
}
}
out.println("\nCall graph:");
this.print(out, false);
}
}
/**
* <tt>CallVisitor</tt> examines the instructions in a method and notices what
* methods are called and which classes are created.
*/
class CallVisitor extends InstructionAdapter {
MethodEditor caller;
CallGraph cg;
boolean firstSpecial; // Are we dealing with the first invokespecial?
private static void db(final String s) {
CallGraph.db(s);
}
public CallVisitor(final CallGraph cg) {
this.cg = cg;
}
public void setCaller(final MethodEditor caller) {
this.caller = caller;
if (caller.isConstructor()) {
this.firstSpecial = true;
} else {
this.firstSpecial = false;
}
}
public void visit_invokevirtual(final Instruction inst) {
CallVisitor.db("\n Visiting Call: " + inst);
this.firstSpecial = false;
// Call doVirtual to determine which methods this call may resolve
// to are live.
final MemberRef callee = (MemberRef) inst.operand();
cg.doVirtual(caller, callee);
}
public void visit_invokeinterface(final Instruction inst) {
CallVisitor.db("\n Visiting Call: " + inst);
this.firstSpecial = false;
// Pretty much the same as invokevirtual
final MemberRef callee = (MemberRef) inst.operand();
cg.doVirtual(caller, callee);
}
public void visit_invokestatic(final Instruction inst) {
CallVisitor.db("\n Visiting call: " + inst);
this.firstSpecial = false;
// There's not a lot to do with static methods since there is no
// dynamic dispatch.
final MemberRef callee = (MemberRef) inst.operand();
cg.addCall(caller, callee);
cg.worklist.add(callee);
}
public void visit_invokespecial(final Instruction inst) {
CallVisitor.db("\n Visiting call: " + inst);
// Recall that invokespecial is used to call constructors, private
// methods, and "super" methods. There is no dynamic dispatch for
// special methods.
final MemberRef callee = (MemberRef) inst.operand();
MethodEditor calleeMethod = null;
try {
calleeMethod = cg.context.editMethod(callee);
} catch (final NoSuchMethodException ex1) {
System.err.println("** Couldn't find method: " + callee);
System.exit(1);
}
if (calleeMethod.isSynchronized() || calleeMethod.isNative()) {
// Calls to synchronized and native methods are virtual
cg.doVirtual(caller, callee);
} else {
// Calls to everything else (superclass methods, private
// methods, etc.) do not involve a dynamic dispatch and can be
// treated like a static method.
cg.addCall(caller, callee);
cg.worklist.add(callee);
}
cg.context.release(calleeMethod.methodInfo());
}
public void visit_getstatic(final Instruction inst) {
// Referencing a static fields implies that its class's static
// initializer has been invoked.
CallVisitor.db("\n Referencing static field " + inst);
final MemberRef field = (MemberRef) inst.operand();
cg.addClinit(field.declaringClass());
}
public void visit_putstatic(final Instruction inst) {
// Referencing a static field implies that its class's static
// initializer has been invoked.
CallVisitor.db("\n Referencing static field " + inst);
final MemberRef field = (MemberRef) inst.operand();
cg.addClinit(field.declaringClass());
}
public void visit_new(final Instruction inst) {
// The new instruction instantiates a type and thus makes it live.
final Type type = (Type) inst.operand();
cg.makeLive(type);
}
}
/**
* Compares <tt>MemberRef</tt>s such that overriding methods are less than
* overridden methods.
*/
class MemberRefComparator implements Comparator {
TypeComparator c;
public MemberRefComparator(final InlineContext context) {
c = new TypeComparator(context);
}
public int compare(final Object o1, final Object o2) {
Assert.isTrue(o1 instanceof MemberRef, o1 + " is not a MemberRef");
Assert.isTrue(o2 instanceof MemberRef, o2 + " is not a MemberRef");
final MemberRef ref1 = (MemberRef) o1;
final MemberRef ref2 = (MemberRef) o2;
final Type type1 = ref1.declaringClass();
final Type type2 = ref2.declaringClass();
return (c.compare(type1, type2));
}
public boolean compareTo(final Object other) {
return (other instanceof MemberRefComparator);
}
}