From 621a95e29173c390b787ab1594257d87dde5b64b Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Sat, 18 Nov 2023 17:26:15 +0100 Subject: [PATCH] works --- .../inf/sp/callgraph/CallGraphRenderer.java | 132 +----------------- .../callgraph/renderer/CallSiteAdapter.java | 27 ++++ .../callgraph/renderer/ClassToMethodEdge.java | 43 ++++++ .../sp/callgraph/renderer/DotClassNode.java | 66 +++++++++ .../inf/sp/callgraph/renderer/DotEdge.java | 16 +++ .../inf/sp/callgraph/renderer/DotGraph.java | 132 ++++++++++++++++++ .../sp/callgraph/renderer/DotMethodNode.java | 41 ++++++ .../inf/sp/callgraph/renderer/DotNode.java | 15 ++ .../callgraph/renderer/DuplicateAwareSet.java | 32 +++++ .../inf/sp/callgraph/renderer/InvokeEdge.java | 60 ++++++++ .../sp/callgraph/renderer/MethodAdapter.java | 39 ++++++ .../inf/sp/callgraph/renderer/MethodLike.java | 43 ++++++ .../renderer/PossibleTargetAdapter.java | 19 +++ .../callgraph/renderer/TypeHierarchyEdge.java | 29 ++++ 14 files changed, 567 insertions(+), 127 deletions(-) create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/CallSiteAdapter.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/ClassToMethodEdge.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/DotClassNode.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/DotEdge.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/DotGraph.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/DotMethodNode.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/DotNode.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/DuplicateAwareSet.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/InvokeEdge.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/MethodAdapter.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/MethodLike.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/PossibleTargetAdapter.java create mode 100644 src/ch/usi/inf/sp/callgraph/renderer/TypeHierarchyEdge.java diff --git a/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java b/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java index 2602607..84d31ae 100644 --- a/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java +++ b/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java @@ -1,5 +1,6 @@ package ch.usi.inf.sp.callgraph; +import ch.usi.inf.sp.callgraph.renderer.DotGraph; import org.objectweb.asm.Opcodes; import java.io.FileWriter; @@ -19,135 +20,12 @@ import java.util.stream.Collectors; */ public final class CallGraphRenderer { - private final Map identifiers = new HashMap<>(); - private final Map 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 { + final DotGraph g = new DotGraph(); + g.build(hierarchy); + final PrintWriter pw = new PrintWriter(new FileWriter(fileName)); - pw.println("digraph CallGraph {"); - pw.println(" rankdir=\"BT\""); - - final ArrayList 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 reverseIdentifiers = identifiers.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); - for (Map.Entry 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.print(g.toDot()); pw.close(); } diff --git a/src/ch/usi/inf/sp/callgraph/renderer/CallSiteAdapter.java b/src/ch/usi/inf/sp/callgraph/renderer/CallSiteAdapter.java new file mode 100644 index 0000000..121cbe1 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/CallSiteAdapter.java @@ -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(); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/ClassToMethodEdge.java b/src/ch/usi/inf/sp/callgraph/renderer/ClassToMethodEdge.java new file mode 100644 index 0000000..5cf595c --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/ClassToMethodEdge.java @@ -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 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); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/DotClassNode.java b/src/ch/usi/inf/sp/callgraph/renderer/DotClassNode.java new file mode 100644 index 0000000..661c5c0 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/DotClassNode.java @@ -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 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); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/DotEdge.java b/src/ch/usi/inf/sp/callgraph/renderer/DotEdge.java new file mode 100644 index 0000000..f024838 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/DotEdge.java @@ -0,0 +1,16 @@ +package ch.usi.inf.sp.callgraph.renderer; + +import java.util.Map; + +public interface DotEdge { + + DotNode getFrom(); + + DotNode getTo(); + + Map getProperties(); + + default String getConnector() { + return "->"; + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/DotGraph.java b/src/ch/usi/inf/sp/callgraph/renderer/DotGraph.java new file mode 100644 index 0000000..08e62b4 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/DotGraph.java @@ -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 classNodes = new DuplicateAwareSet<>(); + private final DuplicateAwareSet methodNodes = new DuplicateAwareSet<>(); + private final List otherEdges = new ArrayList<>(); + private final Set classToMethodEdges = new HashSet<>(); + + private static String formatProperties(Map properties) { + return properties.entrySet().stream() + .map((e) -> String.format("%s=\"%s\"", e.getKey(), e.getValue())) + .collect(Collectors.joining(",")); + } + + private static Map enumerate(Set set) { + final Map 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 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 nodes = new HashSet<>(); + nodes.addAll(classNodes.getAll()); + nodes.addAll(methodNodes.getAll()); + + final Map nodeToId = enumerate(nodes); + + for (final Map.Entry e : nodeToId.entrySet()) { + s.append(buildNode(e.getKey(), e.getValue())); + } + + s.append('\n'); + + final List 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())); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/DotMethodNode.java b/src/ch/usi/inf/sp/callgraph/renderer/DotMethodNode.java new file mode 100644 index 0000000..a810ff8 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/DotMethodNode.java @@ -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 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); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/DotNode.java b/src/ch/usi/inf/sp/callgraph/renderer/DotNode.java new file mode 100644 index 0000000..f42b903 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/DotNode.java @@ -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 getProperties(); +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/DuplicateAwareSet.java b/src/ch/usi/inf/sp/callgraph/renderer/DuplicateAwareSet.java new file mode 100644 index 0000000..1d091db --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/DuplicateAwareSet.java @@ -0,0 +1,32 @@ +package ch.usi.inf.sp.callgraph.renderer; + +import java.util.*; +import java.util.function.Consumer; + +public class DuplicateAwareSet { + + private final Map 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 ifNew) { + final E added = addIfNew(element); + + if (element == added) { + ifNew.accept(element); + } + + return added; + } + + public Set getAll() { + return new HashSet<>(elementsToElements.keySet()); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/InvokeEdge.java b/src/ch/usi/inf/sp/callgraph/renderer/InvokeEdge.java new file mode 100644 index 0000000..70b42e7 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/InvokeEdge.java @@ -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 getProperties() { + return Map.of( + "style", getStyle(), + "color", isDeclared ? "blue" : "red" + ); + } + + public DotMethodNode getFrom() { + return from; + } + + public DotMethodNode getTo() { + return to; + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/MethodAdapter.java b/src/ch/usi/inf/sp/callgraph/renderer/MethodAdapter.java new file mode 100644 index 0000000..23392b7 --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/MethodAdapter.java @@ -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(); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/MethodLike.java b/src/ch/usi/inf/sp/callgraph/renderer/MethodLike.java new file mode 100644 index 0000000..f75372b --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/MethodLike.java @@ -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()); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/PossibleTargetAdapter.java b/src/ch/usi/inf/sp/callgraph/renderer/PossibleTargetAdapter.java new file mode 100644 index 0000000..7bb89bb --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/PossibleTargetAdapter.java @@ -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(); + } +} diff --git a/src/ch/usi/inf/sp/callgraph/renderer/TypeHierarchyEdge.java b/src/ch/usi/inf/sp/callgraph/renderer/TypeHierarchyEdge.java new file mode 100644 index 0000000..dc3c7bb --- /dev/null +++ b/src/ch/usi/inf/sp/callgraph/renderer/TypeHierarchyEdge.java @@ -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 getProperties() { + return Map.of( + "style", superClass.getStyle(), + "color", "black" + ); + } +}