java-callgraph

Details

diff --git a/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java b/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java
index e03d72c..d5dad60 100644
--- a/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java
+++ b/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java
@@ -45,6 +45,7 @@ public class ClassVisitor extends EmptyVisitor {
     private JavaClass clazz;
     private ConstantPoolGen constants;
     private String classReferenceFormat;
+    private final DynamicCallManager DCManager = new DynamicCallManager();
     
     public ClassVisitor(JavaClass jc) {
         clazz = jc;
@@ -55,8 +56,13 @@ public class ClassVisitor extends EmptyVisitor {
     public void visitJavaClass(JavaClass jc) {
         jc.getConstantPool().accept(this);
         Method[] methods = jc.getMethods();
-        for (int i = 0; i < methods.length; i++)
-            methods[i].accept(this);
+        for (int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+            DCManager.retrieveCalls(method, jc);
+            DCManager.linkCalls(method);
+            method.accept(this);
+
+        }
     }
 
     public void visitConstantPool(ConstantPool constantPool) {
diff --git a/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java b/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java
new file mode 100644
index 0000000..5d1275c
--- /dev/null
+++ b/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java
@@ -0,0 +1,116 @@
+package gr.gousiosg.javacg.stat;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.bcel.classfile.Attribute;
+import org.apache.bcel.classfile.BootstrapMethod;
+import org.apache.bcel.classfile.BootstrapMethods;
+import org.apache.bcel.classfile.ConstantMethodHandle;
+import org.apache.bcel.classfile.ConstantMethodref;
+import org.apache.bcel.classfile.ConstantNameAndType;
+import org.apache.bcel.classfile.ConstantPool;
+import org.apache.bcel.classfile.ConstantUtf8;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.classfile.Method;
+
+/**
+ * {@link DynamicCallManager} provides facilities to retrieve information about
+ * dynamic calls statically.
+ * <p>
+ * Most of the time, call relationships are explicit, which allows to properly
+ * build the call graph statically. But in the case of dynamic linking, i.e.
+ * <code>invokedynamic</code> instructions, this relationship might be unknown
+ * until the code is actually executed. Indeed, bootstrap methods are used to
+ * dynamically link the code at first call. One can read details about the
+ * <a href=
+ * "https://docs.oracle.com/javase/8/docs/technotes/guides/vm/multiple-language-support.html#invokedynamic"><code>invokedynamic</code>
+ * instruction</a> to know more about this mechanism.
+ * <p>
+ * Nested lambdas are particularly subject to such absence of concrete caller,
+ * which lead us to produce method names like <code>lambda$null$0</code>, which
+ * breaks the call graph. This information can however be retrieved statically
+ * through the code of the bootstrap method called.
+ * <p>
+ * In {@link #retrieveCalls(Method, JavaClass)}, we retrieve the (called,
+ * caller) relationships by analyzing the code of the caller {@link Method}.
+ * This information is then used in {@link #linkCalls(Method)} to rename the
+ * called {@link Method} properly.
+ * 
+ * @author Matthieu Vergne <matthieu.vergne@gmail.com>
+ *
+ */
+public class DynamicCallManager {
+    private static final Pattern BOOTSTRAP_CALL_PATTERN = Pattern
+            .compile("invokedynamic\t(\\d+):[^:]+:\\S+ \\(\\d+\\)");
+    private static final int CALL_HANDLE_INDEX_ARGUMENT = 1;
+
+    private final Map<String, String> dynamicCallers = new HashMap<>();
+
+    /**
+     * Retrieve dynamic call relationships based on the code of the provided
+     * {@link Method}.
+     * 
+     * @param method
+     *            {@link Method} to analyze the code
+     * @param jc
+     *            {@link JavaClass} info, which contains the bootstrap methods
+     * @see #linkCalls(Method)
+     */
+    public void retrieveCalls(Method method, JavaClass jc) {
+        if (method.isAbstract()) {
+            // No code to consider
+            return;
+        }
+        ConstantPool cp = method.getConstantPool();
+        BootstrapMethod[] boots = getBootstrapMethods(jc);
+        String code = method.getCode().toString();
+        Matcher matcher = BOOTSTRAP_CALL_PATTERN.matcher(code);
+        while (matcher.find()) {
+            int bootIndex = Integer.parseInt(matcher.group(1));
+            BootstrapMethod bootMethod = boots[bootIndex];
+            int calledIndex = bootMethod.getBootstrapArguments()[CALL_HANDLE_INDEX_ARGUMENT];
+            String calledName = getMethodNameFromHandleIndex(cp, calledIndex);
+            String callerName = method.getName();
+            dynamicCallers.put(calledName, callerName);
+        }
+    }
+
+    private String getMethodNameFromHandleIndex(ConstantPool cp, int callIndex) {
+        ConstantMethodHandle handle = (ConstantMethodHandle) cp.getConstant(callIndex);
+        ConstantMethodref ref = (ConstantMethodref) cp.getConstant(handle.getReferenceIndex());
+        ConstantNameAndType nameAndType = (ConstantNameAndType) cp.getConstant(ref.getNameAndTypeIndex());
+        return nameAndType.getName(cp);
+    }
+
+    /**
+     * Link the {@link Method}'s name to its concrete caller if required.
+     * 
+     * @param method
+     *            {@link Method} to analyze
+     * @see #retrieveCalls(Method, JavaClass)
+     */
+    public void linkCalls(Method method) {
+        int nameIndex = method.getNameIndex();
+        ConstantPool cp = method.getConstantPool();
+        String methodName = ((ConstantUtf8) cp.getConstant(nameIndex)).getBytes();
+        String linkedName = methodName;
+        String callerName = methodName;
+        while (linkedName.matches("(lambda\\$)+null(\\$\\d+)+")) {
+            callerName = dynamicCallers.get(callerName);
+            linkedName = linkedName.replace("null", callerName);
+        }
+        cp.setConstant(nameIndex, new ConstantUtf8(linkedName));
+    }
+
+    private BootstrapMethod[] getBootstrapMethods(JavaClass jc) {
+        for (Attribute attribute : jc.getAttributes()) {
+            if (attribute instanceof BootstrapMethods) {
+                return ((BootstrapMethods) attribute).getBootstrapMethods();
+            }
+        }
+        return new BootstrapMethod[] {};
+    }
+}