92 lines
4.0 KiB
Java
92 lines
4.0 KiB
Java
package ch.usi.inf.sp.callgraph;
|
|
|
|
import java.util.ArrayDeque;
|
|
import java.util.Deque;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
import ch.usi.inf.sp.framework.ClassAnalyzer;
|
|
import org.objectweb.asm.Opcodes;
|
|
import org.objectweb.asm.tree.*;
|
|
|
|
|
|
/**
|
|
* Build a call graph (as part of the class hierarchy)
|
|
* consisting of CallSite nodes pointing to Method nodes.
|
|
*
|
|
* @author maggicl@usi.ch
|
|
* @author Matthias.Hauswirth@usi.ch
|
|
*/
|
|
public final class CallGraphBuilder implements ClassAnalyzer {
|
|
|
|
private final ClassHierarchy hierarchy;
|
|
|
|
|
|
public CallGraphBuilder(final ClassHierarchy hierarchy) {
|
|
this.hierarchy = hierarchy;
|
|
}
|
|
|
|
public void analyze(final String location, final ClassNode classNode) {
|
|
try {
|
|
final ClassType type = hierarchy.getOrCreateClass(classNode.name);
|
|
final List<MethodNode> methodNodes = classNode.methods;
|
|
for (final MethodNode methodNode : methodNodes) {
|
|
final Method method = type.getMethod(methodNode.name, methodNode.desc);
|
|
Objects.requireNonNull(method, "method is null");
|
|
final InsnList instructions = methodNode.instructions;
|
|
for (int i = 0; i < instructions.size(); i++) {
|
|
final AbstractInsnNode insn = instructions.get(i);
|
|
|
|
// invokedynamic deliberately ignored
|
|
if (insn.getType() == AbstractInsnNode.METHOD_INSN) {
|
|
final MethodInsnNode methodInsn = (MethodInsnNode) insn;
|
|
final int opCode = methodInsn.getOpcode();
|
|
|
|
final CallSite callSite = new CallSite(insn.getOpcode(), methodInsn.owner, methodInsn.name,
|
|
methodInsn.desc);
|
|
method.addCallSite(callSite);
|
|
|
|
final String targetClazz = callSite.getDeclaredTargetClassName();
|
|
final ClassType clazz = hierarchy.getOrCreateClass(targetClazz);
|
|
// ignoring if a class is unresolved on purpose, as analysis is constrained on user-defined
|
|
// classes (i.e. the contents of pacman-src.jar)
|
|
|
|
if (opCode == Opcodes.INVOKESTATIC || opCode == Opcodes.INVOKESPECIAL) {
|
|
// target is static, no fancy search needed
|
|
callSite.addPossibleTargetClass(clazz); // Cave Johnson, we're done here
|
|
} else if (opCode == Opcodes.INVOKEVIRTUAL) {
|
|
final Deque<ClassType> subClasses = new ArrayDeque<>();
|
|
subClasses.add(clazz);
|
|
|
|
// BFS over class and subclasses and add them all as possible targets
|
|
while (!subClasses.isEmpty()) {
|
|
final ClassType subClazz = subClasses.pop();
|
|
if (!subClazz.isAbstract()) {
|
|
callSite.addPossibleTargetClass(subClazz);
|
|
}
|
|
subClasses.addAll(subClazz.getSubTypes());
|
|
}
|
|
} else { // opCode == Opcodes.INVOKEINTERFACE
|
|
// brute force our way through the type hierarchy to find compatible classes which may
|
|
// implement the interface
|
|
|
|
for (final Type aType : hierarchy.getTypes()) {
|
|
if (aType instanceof ClassType) {
|
|
final ClassType aClazz = (ClassType) aType;
|
|
|
|
if (aClazz.getInterfaces().contains(clazz)) {
|
|
callSite.addPossibleTargetClass(aClazz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (final TypeInconsistencyException ex) {
|
|
System.err.println(ex);
|
|
}
|
|
}
|
|
|
|
}
|