From 8395b10029f38192fe9e55ab37ceb7518372ea6c Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Thu, 16 Nov 2023 00:13:33 +0100 Subject: [PATCH] buggy --- .idea/misc.xml | 1 - .../inf/sp/callgraph/CallGraphRenderer.java | 126 +++++++++++++++++- src/ch/usi/inf/sp/callgraph/Method.java | 13 ++ 3 files changed, 132 insertions(+), 8 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 3048ea5..2796bcc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java b/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java index cf54513..2602607 100644 --- a/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java +++ b/src/ch/usi/inf/sp/callgraph/CallGraphRenderer.java @@ -1,8 +1,14 @@ package ch.usi.inf.sp.callgraph; +import org.objectweb.asm.Opcodes; + import java.io.FileWriter; import java.io.IOException; 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 { - private static String nodeName(ClassType type) { - return type.getInternalName().replaceAll("/", "_"); + 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 static String nodeLabel(ClassType type) { - final String name = type.getInternalName().replaceAll("/", "."); - return name.substring(1, name.length() - 1); + 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 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(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 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.close(); } diff --git a/src/ch/usi/inf/sp/callgraph/Method.java b/src/ch/usi/inf/sp/callgraph/Method.java index 9236822..ae0d0b9 100644 --- a/src/ch/usi/inf/sp/callgraph/Method.java +++ b/src/ch/usi/inf/sp/callgraph/Method.java @@ -2,6 +2,9 @@ package ch.usi.inf.sp.callgraph; import java.util.ArrayList; import java.util.Collection; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.objectweb.asm.Opcodes; @@ -83,6 +86,16 @@ public final class Method { public boolean isFinal() { 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.