gr.gousiosg.javacg.stat.DynamicCallManager Maven / Gradle / Ivy
Show all versions of java-callgraph Show documentation
/*
* Written in 2018 by Matthieu Vergne
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to
* the public domain worldwide. This software is distributed without
* any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not,
* see .
*/
package gr.gousiosg.javacg.stat;
import org.apache.bcel.classfile.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* {@link DynamicCallManager} provides facilities to retrieve information about
* dynamic calls statically.
*
* 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.
* invokedynamic
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
* invokedynamic
* instruction to know more about this mechanism.
*
* Nested lambdas are particularly subject to such absence of concrete caller,
* which lead us to produce method names like lambda$null$0
, which
* breaks the call graph. This information can however be retrieved statically
* through the code of the bootstrap method called.
*
* 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
*/
public class DynamicCallManager {
private static final Pattern BOOTSTRAP_CALL_PATTERN = Pattern
.compile("invokedynamic\t(\\d+):\\S+ \\S+ \\(\\d+\\)");
private static final int CALL_HANDLE_INDEX_ARGUMENT = 1;
private final Map dynamicCallers = new HashMap<>();
// added by adrninistrator
private Set lambdaMethodNameSet = new HashSet<>();
// added end
/**
* 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() || method.isNative()) {
// 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);
// added by adrninistrator
if (calledName.startsWith("lambda$") && !calledName.contains("lambda$null$") && !lambdaMethodNameSet.contains(calledName)) {
lambdaMethodNameSet.add(calledName);
}
// added end
String callerName = method.getName();
dynamicCallers.put(calledName, callerName);
}
}
private String getMethodNameFromHandleIndex(ConstantPool cp, int callIndex) {
ConstantMethodHandle handle = (ConstantMethodHandle) cp.getConstant(callIndex);
ConstantCP ref = (ConstantCP) 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[]{};
}
public Set getLambdaMethodNameSet() {
return lambdaMethodNameSet;
}
public void clearLambdaMethodNameSet() {
lambdaMethodNameSet.clear();
}
}