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.
763 lines
22 KiB
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);
|
|
}
|
|
}
|
|
|