buggy
This commit is contained in:
parent
d2d364bc71
commit
8395b10029
3 changed files with 132 additions and 8 deletions
|
@ -1,4 +1,3 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ASMPluginConfiguration">
|
<component name="ASMPluginConfiguration">
|
||||||
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package ch.usi.inf.sp.callgraph;
|
package ch.usi.inf.sp.callgraph;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,28 +19,134 @@ import java.io.PrintWriter;
|
||||||
*/
|
*/
|
||||||
public final class CallGraphRenderer {
|
public final class CallGraphRenderer {
|
||||||
|
|
||||||
private static String nodeName(ClassType type) {
|
private final Map<String, Integer> identifiers = new HashMap<>();
|
||||||
return type.getInternalName().replaceAll("/", "_");
|
private final Map<Integer, Boolean> nodeBuilt = new HashMap<>();
|
||||||
|
|
||||||
|
private String identifier(String what, boolean fromNode) {
|
||||||
|
return "node" + identifiers.computeIfAbsent(what, (k) -> {
|
||||||
|
int key = identifiers.size() + 1;
|
||||||
|
nodeBuilt.compute(key, (ke, v) -> v == null ? fromNode : (v || fromNode));
|
||||||
|
return key;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String nodeLabel(ClassType type) {
|
private String classNodeName(ClassType type) {
|
||||||
final String name = type.getInternalName().replaceAll("/", ".");
|
return identifier(type.getInternalName(), true);
|
||||||
return name.substring(1, name.length() - 1);
|
}
|
||||||
|
|
||||||
|
private static String methodDescription(Method e) {
|
||||||
|
return (e.getVerboseModifiers() + " " +
|
||||||
|
e.getName().replaceAll("<", "<").replaceAll(">", ">") +
|
||||||
|
e.getDescriptor()).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String classNodeDescriptor(ClassType type) {
|
||||||
|
final String className = type.getInternalName();
|
||||||
|
|
||||||
|
if (!type.isResolved()) {
|
||||||
|
return "[shape=ellipse,style=dashed,label=\"" + className + "\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
final String methods = type.getMethods().stream()
|
||||||
|
.map(CallGraphRenderer::methodDescription)
|
||||||
|
.collect(Collectors.joining("\\n"));
|
||||||
|
|
||||||
|
final String style = type.isAbstract() ? "dashed" : "solid";
|
||||||
|
|
||||||
|
return "[shape=record,style=" + style + ",label=\"{" + className + "|" + methods + "}\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String methodNodeName(ClassHierarchy hierarchy, String className, String name, String descriptor, boolean fromNode) {
|
||||||
|
try {
|
||||||
|
if (!hierarchy.getOrCreateClass(className).isResolved()) {
|
||||||
|
return identifier(className, true);
|
||||||
|
}
|
||||||
|
} catch (TypeInconsistencyException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return identifier(className + " " + name + " " + descriptor, fromNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String methodNodeDescriptor(Method method) {
|
||||||
|
return "[shape=rectangle,style=filled,fillcolor=lightgreen,label=\"" + method.getDeclaringClassName() +
|
||||||
|
"\\n" + methodDescription(method) + "\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String getEdge(String from, String to, int opCode, String color) {
|
||||||
|
String style;
|
||||||
|
switch (opCode) {
|
||||||
|
case Opcodes.INVOKEVIRTUAL: style = "dashed"; break;
|
||||||
|
case Opcodes.INVOKEINTERFACE: style = "dotted"; break;
|
||||||
|
default: style = "solid";
|
||||||
|
}
|
||||||
|
|
||||||
|
return from + " -> " + to + " [style=" + style + ",color=" + color + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dumpDot(final ClassHierarchy hierarchy, final String fileName) throws IOException {
|
public void dumpDot(final ClassHierarchy hierarchy, final String fileName) throws IOException {
|
||||||
final PrintWriter pw = new PrintWriter(new FileWriter(fileName));
|
final PrintWriter pw = new PrintWriter(new FileWriter(fileName));
|
||||||
pw.println("digraph CallGraph {");
|
pw.println("digraph CallGraph {");
|
||||||
pw.println(" rankdir=\"BT\"");
|
pw.println(" rankdir=\"BT\"");
|
||||||
|
|
||||||
|
final ArrayList<String> edges = new ArrayList<>();
|
||||||
|
|
||||||
for (final Type type : hierarchy.getTypes()) {
|
for (final Type type : hierarchy.getTypes()) {
|
||||||
if (type instanceof ClassType) {
|
if (type instanceof ClassType) {
|
||||||
final ClassType classType = (ClassType) type;
|
final ClassType classType = (ClassType) type;
|
||||||
|
final ClassType superClassType = classType.getSuperClass();
|
||||||
|
|
||||||
pw.println(nodeName(classType) + " [shape=record,label=\"" + nodeLabel(classType) + "\"];");
|
pw.println(classNodeName(classType) + " " + classNodeDescriptor(classType) + ";");
|
||||||
|
|
||||||
// TODO: implement this
|
if (superClassType != null) {
|
||||||
|
edges.add(getEdge(classNodeName(classType), classNodeName(superClassType), 0, "black"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final ClassType c : classType.getInterfaces()) {
|
||||||
|
edges.add(getEdge(classNodeName(classType), classNodeName(c), Opcodes.INVOKEVIRTUAL, "black"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Method m : classType.getMethods()) {
|
||||||
|
final String mName = methodNodeName(hierarchy, m.getDeclaringClassName(), m.getName(), m.getDescriptor(), true);
|
||||||
|
|
||||||
|
pw.println(mName + " " + methodNodeDescriptor(m) + ";");
|
||||||
|
edges.add(getEdge(classNodeName(classType), mName, 0, "lightgreen"));
|
||||||
|
|
||||||
|
for (final CallSite c : m.getCallSites()) {
|
||||||
|
final String cName = methodNodeName(hierarchy,
|
||||||
|
c.getDeclaredTargetClassName(),
|
||||||
|
c.getTargetMethodName(),
|
||||||
|
c.getTargetMethodDescriptor(), false);
|
||||||
|
|
||||||
|
edges.add(getEdge(mName, cName, c.getOpcode(), "blue"));
|
||||||
|
|
||||||
|
for (final ClassType p : c.getPossibleTargetClasses()) {
|
||||||
|
final String pName = methodNodeName(hierarchy,
|
||||||
|
p.getInternalName(),
|
||||||
|
c.getTargetMethodName(),
|
||||||
|
c.getTargetMethodDescriptor(), false);
|
||||||
|
|
||||||
|
edges.add(getEdge(mName, pName, c.getOpcode(), "red"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add missing node declarations (happens with methods in classes not scanned)
|
||||||
|
final Map<Integer, String> reverseIdentifiers = identifiers.entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
|
||||||
|
for (Map.Entry<Integer, Boolean> e : nodeBuilt.entrySet()) {
|
||||||
|
if (!e.getValue()) {
|
||||||
|
pw.println("node" + e.getKey() + " [label=\"" + reverseIdentifiers.get(e.getKey()) + "\"]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.append('\n');
|
||||||
|
for (final String edge : edges) {
|
||||||
|
pw.println(edge);
|
||||||
|
}
|
||||||
pw.println("}");
|
pw.println("}");
|
||||||
pw.close();
|
pw.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package ch.usi.inf.sp.callgraph;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
@ -84,6 +87,16 @@ public final class Method {
|
||||||
return (Opcodes.ACC_FINAL & modifiers) !=0;
|
return (Opcodes.ACC_FINAL & modifiers) !=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getVerboseModifiers() {
|
||||||
|
final String visibility = isPublic() ? "public" :
|
||||||
|
isProtected() ? "protected" :
|
||||||
|
isPrivate() ? "private" : null;
|
||||||
|
|
||||||
|
final String kind = isStatic() ? "static" : isAbstract() ? "abstract" : null;
|
||||||
|
|
||||||
|
return Stream.of(visibility, kind).filter(Objects::nonNull).collect(Collectors.joining(" "));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a CallSite to this method.
|
* Add a CallSite to this method.
|
||||||
* Usually done only when really needed (e.g. by CallGraphBuilder).
|
* Usually done only when really needed (e.g. by CallGraphBuilder).
|
||||||
|
|
Reference in a new issue