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 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 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); } } }