How to Find All the Methods That Call a Given Method in Java

How can I find all the methods that call a given method in Java?

For analyzing bytecode, I would recommend ASM. Given a list of Classes to analyze, a visitor can be made which finds the method calls you're interested in. One implementation which analyses classes in a jar file is below.

Note that ASM uses internalNames with '/' instead of '.' as a separator. Specify the target method as a standard declaration without modifiers.

For example, to list methods that could be calling System.out.println("foo") in the java runtime jar:

java -cp "classes;asm-3.1.jar;asm-commons-3.1.jar" App \
c:/java/jdk/jre/lib/rt.jar \
java/io/PrintStream "void println(String)"

Edit: source and line numbers added: Note that this only indicates the last target method invocation per calling method - the original q only wanted to know which methods. I leave it as an exercise for the reader to show line numbers of the calling method declaration, or the line numbers of every target invocation, depending on what you're actually after. :)

results in:

LogSupport.java:44 com/sun/activation/registries/LogSupport log (Ljava/lang/String;)V
LogSupport.java:50 com/sun/activation/registries/LogSupport log (Ljava/lang/String;Ljava/lang/Throwable;)V
...
Throwable.java:498 java/lang/Throwable printStackTraceAsCause (Ljava/io/PrintStream;[Ljava/lang/StackTraceElement;)V
--
885 methods invoke java/io/PrintStream println (Ljava/lang/String;)V

source:

public class App {
private String targetClass;
private Method targetMethod;

private AppClassVisitor cv;

private ArrayList<Callee> callees = new ArrayList<Callee>();

private static class Callee {
String className;
String methodName;
String methodDesc;
String source;
int line;

public Callee(String cName, String mName, String mDesc, String src, int ln) {
className = cName; methodName = mName; methodDesc = mDesc; source = src; line = ln;
}
}

private class AppMethodVisitor extends MethodAdapter {

boolean callsTarget;
int line;

public AppMethodVisitor() { super(new EmptyVisitor()); }

public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (owner.equals(targetClass)
&& name.equals(targetMethod.getName())
&& desc.equals(targetMethod.getDescriptor())) {
callsTarget = true;
}
}

public void visitCode() {
callsTarget = false;
}

public void visitLineNumber(int line, Label start) {
this.line = line;
}

public void visitEnd() {
if (callsTarget)
callees.add(new Callee(cv.className, cv.methodName, cv.methodDesc,
cv.source, line));
}
}

private class AppClassVisitor extends ClassAdapter {

private AppMethodVisitor mv = new AppMethodVisitor();

public String source;
public String className;
public String methodName;
public String methodDesc;

public AppClassVisitor() { super(new EmptyVisitor()); }

public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
className = name;
}

public void visitSource(String source, String debug) {
this.source = source;
}

public MethodVisitor visitMethod(int access, String name,
String desc, String signature,
String[] exceptions) {
methodName = name;
methodDesc = desc;

return mv;
}
}

public void findCallingMethodsInJar(String jarPath, String targetClass,
String targetMethodDeclaration) throws Exception {

this.targetClass = targetClass;
this.targetMethod = Method.getMethod(targetMethodDeclaration);

this.cv = new AppClassVisitor();

JarFile jarFile = new JarFile(jarPath);
Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

if (entry.getName().endsWith(".class")) {
InputStream stream = new BufferedInputStream(jarFile.getInputStream(entry), 1024);
ClassReader reader = new ClassReader(stream);

reader.accept(cv, 0);

stream.close();
}
}
}

public static void main( String[] args ) {
try {
App app = new App();

app.findCallingMethodsInJar(args[0], args[1], args[2]);

for (Callee c : app.callees) {
System.out.println(c.source+":"+c.line+" "+c.className+" "+c.methodName+" "+c.methodDesc);
}

System.out.println("--\n"+app.callees.size()+" methods invoke "+
app.targetClass+" "+
app.targetMethod.getName()+" "+app.targetMethod.getDescriptor());
} catch(Exception x) {
x.printStackTrace();
}
}

}

how to find all methods called in a method?

I would do this with javassist.

So let's say you have the following class accessible in your classpath and want to find all methods invoked from getItem1():

class MyClass {
public String getItem1() throws UnsupportedEncodingException{
String a = "2";
a.getBytes();
a.getBytes("we");
System.out.println(a);
int t = Integer.parseInt(a);
return a;
}
}

And you have this MyClass compiled.
Create another class that uses javassist api:

public class MethodFinder {

public static void main(String[] args) throws Throwable {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("MyClass");
CtMethod method = ctClass.getDeclaredMethod("getItem1");
method.instrument(
new ExprEditor() {
public void edit(MethodCall m)
throws CannotCompileException
{
System.out.println(m.getClassName() + "." + m.getMethodName() + " " + m.getSignature());
}
});
}
}

the output of the MethodFinder run is:

java.lang.String.getBytes ()[B   
java.lang.String.getBytes (Ljava/lang/String;)[B
java.io.PrintStream.println (Ljava/lang/String;)V
java.lang.Integer.parseInt (Ljava/lang/String;)I

how to find all methods called in a methods including Lamda?

Since String.valueOf is not actually invoked by the method, I’d use a broader term, e.g. talk about referenced methods which includes invoked methods. One way to gather all of them using ASM(java-bytecode-asm) is:

import java.io.IOException;
import java.util.Optional;
import org.objectweb.asm.*;

public class FindAllReferencedMethods {
class Example {
void create() {
System.out.println("TEST");
Optional.of("String").map(String::valueOf).get();
}
}
public static void main(String[] args) throws IOException {
ClassReader r = new ClassReader(Example.class.getName());
r.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String sig, String[] ex) {
return name.equals("create")? new MethodRefCollector(): null;
}

}, ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
}

static void referencedMethod(String owner, String name, String desc) {
System.out.println(
Type.getObjectType(owner).getClassName() + "." + name + " " + desc);
}

static class MethodRefCollector extends MethodVisitor {
public MethodRefCollector() {
super(Opcodes.ASM5);
}

@Override
public void visitMethodInsn(
int opcode, String owner, String name, String desc, boolean itf) {
referencedMethod(owner, name, desc);
}

@Override
public void visitInvokeDynamicInsn(
String name, String desc, Handle bsm, Object... bsmArgs) {
if(bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")
&& bsm.getDesc().equals(bsm.getName().equals("altMetafactory")?
ALT_SIG: MF_SIG)) {
Handle target = (Handle)bsmArgs[1];
referencedMethod(target.getOwner(), target.getName(), target.getDesc());
}
}
}
static String MF_SIG = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;"
+"Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/"
+"MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
static String ALT_SIG = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;"
+"Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;";
}

How to find all callers of all methods in a specified class?

You can achieve something like above in your question by making use of JavaAssist library as given here : Java Assist - Java bytecode engineering toolkit

This Jar file provides you with methods that can help you inspect your classes and its methods much more deeply to fetch that kind of information (i.e , methods in a given class are invoking method from which particular class) . For example , lets take example of this class A as below :

public class A {

void meth1()
{
System.out.println("meth1 A");
}

void meth2()
{
System.out.println("meth2 A");
}

void meth3()
{
System.out.println("meth3 A");
}
}

Now , let's take another class B , which has also 3 methods . Inside each of these 3 methods , methods of A are being called as below :

public class B {

A a =new A();
void meth1()
{
System.out.println("meth1 B");
a.meth1();
}

void meth2()
{
System.out.println("meth2 B");
a.meth2();
}

void meth3()
{
System.out.println("meth3 B");
a.meth3();
}
}

As you can see , methods in class B are invoking methods of class A from inside them . Now, we can make use of methods of JavaAssist library as shown below , to fetch the information that from each method of class B , which method of A has been called ? The method.instrument construct from the JavaAssist library allows you to build a bytecode manipulation expression , which helps you to list methods invoked per method and the classes to which those method belong . As a demo for your problem , i am adding the mapping to the method invocations in the methodToInvokedMethodMap , where key is the source class method from where method is being invoked , and value is the method which class information that is being invoked from inside method B , just like you asked in your question :) . Adding the code for the class C , which displays this mapping as below :

import javassist.*;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class C {

static Map<String, String> methodToInvokedMethodMap = new HashMap<>();

public static void main(String[] args) throws NotFoundException, CannotCompileException {

Class thisClass = B.class;
Method[] methods = thisClass.getDeclaredMethods();

for (int i = 0; i < methods.length; i++) {

ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("B");

String currentMethod = methods[i].getName().toString();
CtMethod method = ctClass.getDeclaredMethod(methods[i].getName().toString());
method.instrument(new ExprEditor() {
public void edit(MethodCall m) throws CannotCompileException {

if (m.getClassName().equals("A")) {
methodToInvokedMethodMap.put("Class B method " + currentMethod, m.getClassName() + " with name as : " + m.getMethodName());
}
}
});

methodToInvokedMethodMap.forEach((key, value) -> {
System.out.println(key + " invokes method from class " + value);
});
}
}
}

You can further explore JavaAssist library to find out different methods to serve your usecase . Also , in your Spring Boot project you can use this Jar as it is , or you can convert this jar as a Maven Dependency in your project and use it as a dependency via your pom.xml file . Hope this helps you resolve the problem you asked for :) .

Also , i'm attaching here how the output looks like for this :

Sample Image

How to get list of methods defined in another class called from a given method in Java

I used an open source byte code manipulator called Javassists which already has an API to fetch method calls made in a given method. It also has method to fetch the code attribute which can give you the no of lines in a given method.

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
public static void main(String[] args)
{
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = null;
try {
ctClass = cp.get(className);
} catch (NotFoundException e) {
throw new RuntimeException(e);
}

CtMethod ctMethod = ctClass.getMethod(methodName);

ctMethod.instrument(
new ExprEditor() {
public void edit(MethodCall calledMethod) {
System.out.println("Method "+ calledMethod.getMethod().getName() + " is called inside "+methodName);
}
});
}


Related Topics



Leave a reply



Submit