works
This commit is contained in:
parent
8395b10029
commit
621a95e291
14 changed files with 567 additions and 127 deletions
|
@ -1,5 +1,6 @@
|
||||||
package ch.usi.inf.sp.callgraph;
|
package ch.usi.inf.sp.callgraph;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.renderer.DotGraph;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
|
@ -19,135 +20,12 @@ import java.util.stream.Collectors;
|
||||||
*/
|
*/
|
||||||
public final class CallGraphRenderer {
|
public final class CallGraphRenderer {
|
||||||
|
|
||||||
private final Map<String, Integer> identifiers = new HashMap<>();
|
|
||||||
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 String classNodeName(ClassType type) {
|
|
||||||
return identifier(type.getInternalName(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 DotGraph g = new DotGraph();
|
||||||
|
g.build(hierarchy);
|
||||||
|
|
||||||
final PrintWriter pw = new PrintWriter(new FileWriter(fileName));
|
final PrintWriter pw = new PrintWriter(new FileWriter(fileName));
|
||||||
pw.println("digraph CallGraph {");
|
pw.print(g.toDot());
|
||||||
pw.println(" rankdir=\"BT\"");
|
|
||||||
|
|
||||||
final ArrayList<String> edges = new ArrayList<>();
|
|
||||||
|
|
||||||
for (final Type type : hierarchy.getTypes()) {
|
|
||||||
if (type instanceof ClassType) {
|
|
||||||
final ClassType classType = (ClassType) type;
|
|
||||||
final ClassType superClassType = classType.getSuperClass();
|
|
||||||
|
|
||||||
pw.println(classNodeName(classType) + " " + classNodeDescriptor(classType) + ";");
|
|
||||||
|
|
||||||
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.close();
|
pw.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
src/ch/usi/inf/sp/callgraph/renderer/CallSiteAdapter.java
Normal file
27
src/ch/usi/inf/sp/callgraph/renderer/CallSiteAdapter.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.CallSite;
|
||||||
|
|
||||||
|
public class CallSiteAdapter extends MethodLike {
|
||||||
|
|
||||||
|
private final CallSite callSite;
|
||||||
|
|
||||||
|
public CallSiteAdapter(CallSite callSite) {
|
||||||
|
this.callSite = callSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeclaringClass() {
|
||||||
|
return callSite.getDeclaredTargetClassName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return callSite.getTargetMethodName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescriptor() {
|
||||||
|
return callSite.getTargetMethodDescriptor();
|
||||||
|
}
|
||||||
|
}
|
43
src/ch/usi/inf/sp/callgraph/renderer/ClassToMethodEdge.java
Normal file
43
src/ch/usi/inf/sp/callgraph/renderer/ClassToMethodEdge.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ClassToMethodEdge implements DotEdge {
|
||||||
|
private final DotClassNode from;
|
||||||
|
private final DotMethodNode to;
|
||||||
|
|
||||||
|
public ClassToMethodEdge(DotClassNode from, DotMethodNode to) {
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DotClassNode getFrom() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DotMethodNode getTo() {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getProperties() {
|
||||||
|
return Map.of(
|
||||||
|
"style", "solid",
|
||||||
|
"color", "lightgreen"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ClassToMethodEdge that = (ClassToMethodEdge) o;
|
||||||
|
return Objects.equals(from, that.from) && Objects.equals(to, that.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(from, to);
|
||||||
|
}
|
||||||
|
}
|
66
src/ch/usi/inf/sp/callgraph/renderer/DotClassNode.java
Normal file
66
src/ch/usi/inf/sp/callgraph/renderer/DotClassNode.java
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.ClassType;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DotClassNode implements DotNode {
|
||||||
|
|
||||||
|
private final ClassType type;
|
||||||
|
|
||||||
|
public DotClassNode(ClassType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isResolved() {
|
||||||
|
return type.isResolved();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassType getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStyle() {
|
||||||
|
return type.isInterface() ? "dotted" : type.isAbstract() ? "dashed" : "solid";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getProperties() {
|
||||||
|
final String className = type.getInternalName();
|
||||||
|
|
||||||
|
if (!isResolved()) {
|
||||||
|
// "External" classes (like java.lang.Math) are just ellipsis
|
||||||
|
return Map.of(
|
||||||
|
"shape", "ellipse",
|
||||||
|
"style", getStyle(),
|
||||||
|
"label", className
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String methods = type.getMethods().stream()
|
||||||
|
.map(MethodAdapter::new)
|
||||||
|
.map(MethodLike::prettyName)
|
||||||
|
.collect(Collectors.joining("\\n"));
|
||||||
|
|
||||||
|
return Map.of(
|
||||||
|
"shape", "record",
|
||||||
|
"style", getStyle(),
|
||||||
|
"label", DotNode.recordSplit(className, methods)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DotClassNode that = (DotClassNode) o;
|
||||||
|
return Objects.equals(type, that.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(type);
|
||||||
|
}
|
||||||
|
}
|
16
src/ch/usi/inf/sp/callgraph/renderer/DotEdge.java
Normal file
16
src/ch/usi/inf/sp/callgraph/renderer/DotEdge.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface DotEdge {
|
||||||
|
|
||||||
|
DotNode getFrom();
|
||||||
|
|
||||||
|
DotNode getTo();
|
||||||
|
|
||||||
|
Map<String, String> getProperties();
|
||||||
|
|
||||||
|
default String getConnector() {
|
||||||
|
return "->";
|
||||||
|
}
|
||||||
|
}
|
132
src/ch/usi/inf/sp/callgraph/renderer/DotGraph.java
Normal file
132
src/ch/usi/inf/sp/callgraph/renderer/DotGraph.java
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DotGraph {
|
||||||
|
// use map[e->e] instead of set[e] to allow fetching equal element
|
||||||
|
private final DuplicateAwareSet<DotClassNode> classNodes = new DuplicateAwareSet<>();
|
||||||
|
private final DuplicateAwareSet<DotMethodNode> methodNodes = new DuplicateAwareSet<>();
|
||||||
|
private final List<DotEdge> otherEdges = new ArrayList<>();
|
||||||
|
private final Set<DotEdge> classToMethodEdges = new HashSet<>();
|
||||||
|
|
||||||
|
private static String formatProperties(Map<String, String> properties) {
|
||||||
|
return properties.entrySet().stream()
|
||||||
|
.map((e) -> String.format("%s=\"%s\"", e.getKey(), e.getValue()))
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <E> Map<E, Integer> enumerate(Set<E> set) {
|
||||||
|
final Map<E, Integer> map = new HashMap<>();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for (final E element : set) {
|
||||||
|
map.put(element, index++);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DotClassNode considerClass(final ClassType classType) {
|
||||||
|
return classNodes.addIfNew(new DotClassNode(classType), (addedClassNode) -> {
|
||||||
|
if (addedClassNode.isResolved()) {
|
||||||
|
for (final Method m : classType.getMethods()) {
|
||||||
|
methodNodes.addIfNew(
|
||||||
|
new DotMethodNode(new MethodAdapter(m)),
|
||||||
|
(addedMethodNode) -> classToMethodEdges.add(new ClassToMethodEdge(addedClassNode, addedMethodNode))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final ClassType c : classType.getInterfaces()) {
|
||||||
|
final DotClassNode interfaceNode = considerClass(c);
|
||||||
|
otherEdges.add(new TypeHierarchyEdge(addedClassNode, interfaceNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanMethods() {
|
||||||
|
final Deque<DotMethodNode> methodQueue = new ArrayDeque<>(methodNodes.getAll());
|
||||||
|
|
||||||
|
while (!methodQueue.isEmpty()) {
|
||||||
|
final DotMethodNode methodNode = methodQueue.pop();
|
||||||
|
final Method m = methodNode.getMethodLike().getMethod();
|
||||||
|
if (m == null) continue; // we care about actual methods only, not callsites
|
||||||
|
|
||||||
|
for (final CallSite c : m.getCallSites()) {
|
||||||
|
final DotMethodNode toMethodNode = methodNodes.addIfNew(new DotMethodNode(new CallSiteAdapter(c)));
|
||||||
|
otherEdges.add(new InvokeEdge(methodNode, toMethodNode, c.getOpcode(), true));
|
||||||
|
|
||||||
|
for (final ClassType p : c.getPossibleTargetClasses()) {
|
||||||
|
if (p.getInternalName().endsWith("Math")) {
|
||||||
|
System.out.println("aaa");
|
||||||
|
}
|
||||||
|
|
||||||
|
final DotMethodNode pm = methodNodes.addIfNew(new DotMethodNode(new PossibleTargetAdapter(c, p)));
|
||||||
|
final DotClassNode pc = classNodes.addIfNew(new DotClassNode(p));
|
||||||
|
|
||||||
|
// if the target is a new node, the class of that node might not have been analyzed,
|
||||||
|
// thus class to method edges might not have been added
|
||||||
|
classToMethodEdges.add(new ClassToMethodEdge(pc, pm));
|
||||||
|
}
|
||||||
|
|
||||||
|
otherEdges.add(new InvokeEdge(methodNode, toMethodNode, c.getOpcode(), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void build(final ClassHierarchy hierarchy) {
|
||||||
|
for (final Type type : hierarchy.getTypes()) {
|
||||||
|
if (type instanceof ClassType) {
|
||||||
|
final ClassType classType = (ClassType) type;
|
||||||
|
final DotClassNode node = considerClass(classType);
|
||||||
|
|
||||||
|
final ClassType superClassType = classType.getSuperClass();
|
||||||
|
if (superClassType != null) {
|
||||||
|
final DotClassNode superNode = considerClass(superClassType);
|
||||||
|
otherEdges.add(new TypeHierarchyEdge(node, superNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanMethods();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toDot() {
|
||||||
|
final StringBuilder s = new StringBuilder();
|
||||||
|
s.append("digraph CallGraph {\n");
|
||||||
|
s.append("rankdir=\"BT\"\n");
|
||||||
|
|
||||||
|
final Set<DotNode> nodes = new HashSet<>();
|
||||||
|
nodes.addAll(classNodes.getAll());
|
||||||
|
nodes.addAll(methodNodes.getAll());
|
||||||
|
|
||||||
|
final Map<DotNode, Integer> nodeToId = enumerate(nodes);
|
||||||
|
|
||||||
|
for (final Map.Entry<DotNode, Integer> e : nodeToId.entrySet()) {
|
||||||
|
s.append(buildNode(e.getKey(), e.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
s.append('\n');
|
||||||
|
|
||||||
|
final List<DotEdge> edges = new ArrayList<>(otherEdges);
|
||||||
|
edges.addAll(classToMethodEdges);
|
||||||
|
|
||||||
|
for (final DotEdge e : edges) {
|
||||||
|
s.append(buildEdge(e, nodeToId.get(e.getFrom()), nodeToId.get(e.getTo())));
|
||||||
|
}
|
||||||
|
|
||||||
|
s.append("}\n");
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildNode(DotNode node, int nodeId) {
|
||||||
|
return String.format("node%d [%s];\n", nodeId, formatProperties(node.getProperties()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildEdge(DotEdge edge, int idFrom, int idTo) {
|
||||||
|
return String.format("node%d %s node%d [%s];\n", idFrom, edge.getConnector(), idTo, formatProperties(edge.getProperties()));
|
||||||
|
}
|
||||||
|
}
|
41
src/ch/usi/inf/sp/callgraph/renderer/DotMethodNode.java
Normal file
41
src/ch/usi/inf/sp/callgraph/renderer/DotMethodNode.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class DotMethodNode implements DotNode {
|
||||||
|
|
||||||
|
private static final String METHOD_COLOR = "lightgreen";
|
||||||
|
private final MethodLike method;
|
||||||
|
|
||||||
|
public DotMethodNode(MethodLike method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodLike getMethodLike() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getProperties() {
|
||||||
|
return Map.of(
|
||||||
|
"shape", "rectangle",
|
||||||
|
"style", "filled",
|
||||||
|
"fillcolor", METHOD_COLOR,
|
||||||
|
"label", String.format("%s\\n%s", method.getDeclaringClass(), method.prettyName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DotMethodNode that = (DotMethodNode) o;
|
||||||
|
return Objects.equals(method, that.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(method);
|
||||||
|
}
|
||||||
|
}
|
15
src/ch/usi/inf/sp/callgraph/renderer/DotNode.java
Normal file
15
src/ch/usi/inf/sp/callgraph/renderer/DotNode.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.Method;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public interface DotNode {
|
||||||
|
static String recordSplit(String... elements) {
|
||||||
|
return Arrays.stream(elements).collect(Collectors.joining("|", "{", "}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> getProperties();
|
||||||
|
}
|
32
src/ch/usi/inf/sp/callgraph/renderer/DuplicateAwareSet.java
Normal file
32
src/ch/usi/inf/sp/callgraph/renderer/DuplicateAwareSet.java
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class DuplicateAwareSet<E> {
|
||||||
|
|
||||||
|
private final Map<E, E> elementsToElements = new HashMap<>();
|
||||||
|
|
||||||
|
public E addIfNew(final E element) {
|
||||||
|
if (elementsToElements.containsKey(element)) {
|
||||||
|
return elementsToElements.get(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
elementsToElements.put(element, element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E addIfNew(final E element, Consumer<E> ifNew) {
|
||||||
|
final E added = addIfNew(element);
|
||||||
|
|
||||||
|
if (element == added) {
|
||||||
|
ifNew.accept(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<E> getAll() {
|
||||||
|
return new HashSet<>(elementsToElements.keySet());
|
||||||
|
}
|
||||||
|
}
|
60
src/ch/usi/inf/sp/callgraph/renderer/InvokeEdge.java
Normal file
60
src/ch/usi/inf/sp/callgraph/renderer/InvokeEdge.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class InvokeEdge implements DotEdge {
|
||||||
|
private final DotMethodNode from;
|
||||||
|
|
||||||
|
private final DotMethodNode to;
|
||||||
|
|
||||||
|
private final int invokeOpCode;
|
||||||
|
|
||||||
|
private final boolean isDeclared;
|
||||||
|
|
||||||
|
public InvokeEdge(DotMethodNode from, DotMethodNode to, int invokeOpCode, boolean isDeclared) {
|
||||||
|
if (invokeOpCode != Opcodes.INVOKESTATIC &&
|
||||||
|
invokeOpCode != Opcodes.INVOKESPECIAL &&
|
||||||
|
invokeOpCode != Opcodes.INVOKEVIRTUAL &&
|
||||||
|
invokeOpCode != Opcodes.INVOKEINTERFACE) {
|
||||||
|
throw new IllegalArgumentException("invokeOpCode must be a invoke instruction opcode");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
this.invokeOpCode = invokeOpCode;
|
||||||
|
this.isDeclared = isDeclared;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStyle() {
|
||||||
|
switch (invokeOpCode) {
|
||||||
|
case Opcodes.INVOKESTATIC:
|
||||||
|
case Opcodes.INVOKESPECIAL:
|
||||||
|
return "solid";
|
||||||
|
case Opcodes.INVOKEVIRTUAL:
|
||||||
|
return "dashed";
|
||||||
|
case Opcodes.INVOKEINTERFACE:
|
||||||
|
return "dotted";
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getProperties() {
|
||||||
|
return Map.of(
|
||||||
|
"style", getStyle(),
|
||||||
|
"color", isDeclared ? "blue" : "red"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DotMethodNode getFrom() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DotMethodNode getTo() {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
}
|
39
src/ch/usi/inf/sp/callgraph/renderer/MethodAdapter.java
Normal file
39
src/ch/usi/inf/sp/callgraph/renderer/MethodAdapter.java
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.Method;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class MethodAdapter extends MethodLike {
|
||||||
|
|
||||||
|
private final Method method;
|
||||||
|
|
||||||
|
public MethodAdapter(Method method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Method getMethod() {
|
||||||
|
return this.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVerboseModifiers() {
|
||||||
|
return method.getVerboseModifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeclaringClass() {
|
||||||
|
return method.getDeclaringClassName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return method.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescriptor() {
|
||||||
|
return method.getDescriptor();
|
||||||
|
}
|
||||||
|
}
|
43
src/ch/usi/inf/sp/callgraph/renderer/MethodLike.java
Normal file
43
src/ch/usi/inf/sp/callgraph/renderer/MethodLike.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.Method;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public abstract class MethodLike {
|
||||||
|
public String getVerboseModifiers() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getMethod() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getDeclaringClass();
|
||||||
|
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
public abstract String getDescriptor();
|
||||||
|
|
||||||
|
public String prettyName() {
|
||||||
|
return (getVerboseModifiers() + " " +
|
||||||
|
getName().replaceAll("<", "<").replaceAll(">", ">") +
|
||||||
|
getDescriptor()).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toSerializedString() {
|
||||||
|
return String.format("%s.%s %s", getDeclaringClass(), getName(), getDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(toSerializedString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof MethodLike)) return false;
|
||||||
|
final MethodLike m = (MethodLike) obj;
|
||||||
|
return Objects.equals(toSerializedString(), m.toSerializedString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import ch.usi.inf.sp.callgraph.CallSite;
|
||||||
|
import ch.usi.inf.sp.callgraph.ClassType;
|
||||||
|
|
||||||
|
public class PossibleTargetAdapter extends CallSiteAdapter {
|
||||||
|
|
||||||
|
private final ClassType possibleTarget;
|
||||||
|
|
||||||
|
public PossibleTargetAdapter(CallSite callSite, ClassType possibleTarget) {
|
||||||
|
super(callSite);
|
||||||
|
this.possibleTarget = possibleTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeclaringClass() {
|
||||||
|
return possibleTarget.getInternalName();
|
||||||
|
}
|
||||||
|
}
|
29
src/ch/usi/inf/sp/callgraph/renderer/TypeHierarchyEdge.java
Normal file
29
src/ch/usi/inf/sp/callgraph/renderer/TypeHierarchyEdge.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package ch.usi.inf.sp.callgraph.renderer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class TypeHierarchyEdge implements DotEdge {
|
||||||
|
private final DotClassNode subClass;
|
||||||
|
private final DotClassNode superClass;
|
||||||
|
|
||||||
|
public TypeHierarchyEdge(DotClassNode subClass, DotClassNode superClass) {
|
||||||
|
this.subClass = subClass;
|
||||||
|
this.superClass = superClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DotClassNode getFrom() {
|
||||||
|
return subClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DotClassNode getTo() {
|
||||||
|
return superClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getProperties() {
|
||||||
|
return Map.of(
|
||||||
|
"style", superClass.getStyle(),
|
||||||
|
"color", "black"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue