36

I need to get a list of all caller methods for a method of interest for me in Java. Is there a tool that can help me with this?

Edit: I forgot to mention that I need to do this from a program. I'm usig Java Pathfinder and I want to run it an all the methods that call my method of interest.

Kara
  • 6,115
  • 16
  • 50
  • 57
arthur
  • 393
  • 1
  • 3
  • 9
  • 3
    Guys miklosar did edit the q and says this needs to occur at runtime, there appear to be countless people saying how to do this in an IDE. Up vote @chadwick – mP. May 31 '09 at 07:07
  • 1
    perhaps you could explain why you need to do this at runtime as people may not have come across this requirement. – Peter Lawrey Jun 01 '09 at 20:51

11 Answers11

48

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();
        }
    }

}
Chadwick
  • 12,555
  • 7
  • 49
  • 66
  • 1
    This is exactly what I'm looking for. Is there any way to also find the source line of the methods? – arthur May 31 '09 at 10:55
  • With debug info in the jar, you'll see the visitSource and visitLineNumber methods invoked (be sure not to disable DEBUG info in the accept call, as I originally had). Not sure why full path to the source is missing, that may be a compiler issue, or maybe there's an annotation for it. Note that this only shows the last target method invocation per method - your original q only wanted to know which methods. Minor effort would show the line number of the method declaration if thats what you're after. – Chadwick May 31 '09 at 17:03
  • This code only returns one call for each calling method; this was probably originally intended, but with the addition of the line numbers, it now seems more like a bug... – daphshez Jun 05 '09 at 15:49
  • @Daphna Shezaf That fact is highlighted in my comment just before yours, but I'll edit the answer above to include that caveat. I leave it as an exercise for the reader to customize this sample code for any real-world situations :) – Chadwick Jun 05 '09 at 22:19
  • 1
    +1 to this response. Have just modified it into a custom cross-ref tool which is shockingly fast. Thanks! – Sarge Apr 27 '11 at 23:28
  • If in test.jar file contains 3 following classes: ```class A { void f() {} } class B extends A {} class C { X x; void call1() { new A().f(); } void call2() { x.f(); } void call3() { new B().f(); } }``` Then run: `java -cp "asm-all-5.1.jar;." App test.jar A "void f()"` will only found call1 & call2, not found call3 – Bùi Việt Thành Apr 03 '16 at 16:08
  • And if `class C` then only call1 is found! – Bùi Việt Thành Apr 03 '16 at 16:14
  • Thank you very much, but can you provide `imports statements` – Anas Jan 13 '19 at 14:47
  • @Anas I have problems with that as well. I think it is outdated, it seems `MethodAdapter` for example no longer exists. – clankill3r Mar 05 '19 at 12:00
  • Thanks for interesting (:, I used Apache BCEL – Anas Mar 05 '19 at 18:32
12

Edit: the original question was edited to indicate a runtime solution was needed - this answer was given before that edit and only indicates how to do it during development.

If you are using Eclipse you can right click the method and choose "Open call hierarchy" to get this information.

Updated after reading comments: Other IDEs support this as well in a similar fashion (at least Netbeans and IntelliJ do)

Simon Groenewolt
  • 10,607
  • 1
  • 36
  • 64
5
  1. right click on method
  2. Go to references and (depending on your requirement)
    choose workspace/project/Hierarchy.

This pops up a panel that shows all references to this functions. Eclipse FTW !

Smamatti
  • 3,901
  • 3
  • 32
  • 43
Bharath V
  • 51
  • 1
  • 1
5

Annotate the method with @Deprecated ( or tag it with @deprecated ), turn on deprecation warnings, run your compile and see which warnings get triggered.

The run your compile bit can be done either by invoking an external ant process or by using the Java 6 compiler API.

Robert Munteanu
  • 67,031
  • 36
  • 206
  • 278
4

In eclipse, highlight the method name and then Ctrl+Shift+G

Niyaz
  • 53,943
  • 55
  • 151
  • 182
3

There isn't a way to do this (programmatically) via the Java reflection libraries - you can't ask a java.lang.reflect.Method "which methods do you call?"

That leaves two other options I can think of:

  1. Static analysis of the source code. I'm sure this is what the Eclipse Java toolset does - you could look at the Eclipse source behind the JDT, and find what it does when you ask Eclipse to "Find References" to a method.

  2. Bytecode analysis. You could inspect the bytecode for calls to the method. I'm not sure what libraries or examples are out there to help with this - but I can't imagine that something doesn't exist.

Jared
  • 25,520
  • 24
  • 79
  • 114
1

I made a small example using @Chadwick's one. It's a test that assesses if calls to getDatabaseEngine() are made by methods that implement @Transaction.

/**
 * Ensures that methods that call {@link DatabaseProvider#getDatabaseEngine()}
 * implement the {@link @Transaction} annotation.
 *
 * @throws Exception If something occurs while testing.
 */
@Test
public void ensure() throws Exception {
    final Method method = Method.getMethod(
            DatabaseEngine.class.getCanonicalName() + " getDatabaseEngine()");

    final ArrayList<java.lang.reflect.Method> faultyMethods = Lists.newArrayList();

    for (Path p : getAllClasses()) {
        try (InputStream stream = new BufferedInputStream(Files.newInputStream(p))) {
            ClassReader reader = new ClassReader(stream);


            reader.accept(new ClassAdapter(new EmptyVisitor()) {
                @Override
                public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {

                    return new MethodAdapter(new EmptyVisitor()) {
                        @Override
                        public void visitMethodInsn(int opcode, String owner, String nameCode, String descCode) {
                            try {
                                final Class<?> klass = Class.forName(Type.getObjectType(owner).getClassName());
                                if (DatabaseProvider.class.isAssignableFrom(klass) &&
                                        nameCode.equals(method.getName()) &&
                                        descCode.equals(method.getDescriptor())) {

                                    final java.lang.reflect.Method method = klass.getDeclaredMethod(name,
                                            getParameters(desc).toArray(new Class[]{}));

                                    for (Annotation annotation : method.getDeclaredAnnotations()) {
                                        if (annotation.annotationType().equals(Transaction.class)) {
                                            return;
                                        }
                                    }

                                    faultyMethods.add(method);

                                }
                            } catch (Exception e) {
                                Throwables.propagate(e);
                            }
                        }
                    };
                }
            }, 0);

        }
    }

    if (!faultyMethods.isEmpty()) {
        fail("\n\nThe following methods must implement @Transaction because they're calling getDatabaseEngine().\n\n" + Joiner.on("\n").join
                (faultyMethods) + "\n\n");
    }

}

/**
 * Gets all the classes from target.
 *
 * @return The list of classes.
 * @throws IOException If something occurs while collecting those classes.
 */
private List<Path> getAllClasses() throws IOException {
    final ImmutableList.Builder<Path> builder = new ImmutableList.Builder<>();
    Files.walkFileTree(Paths.get("target", "classes"), new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
            if (file.getFileName().toString().endsWith(".class")) {
                builder.add(file);
            }
            return FileVisitResult.CONTINUE;
        }
    });

    return builder.build();
}

/**
 * Gets the list of parameters given the description.
 *
 * @param desc The method description.
 * @return The list of parameters.
 * @throws Exception If something occurs getting the parameters.
 */
private List<Class<?>> getParameters(String desc) throws Exception {
    ImmutableList.Builder<Class<?>> obj = new ImmutableList.Builder<>();

    for (Type type : Type.getArgumentTypes(desc)) {
        obj.add(ClassUtils.getClass(type.getClassName()));
    }

    return obj.build();
}
rpvilao
  • 1,116
  • 2
  • 14
  • 31
1

1)In eclipse it is ->right click on the method and select open call hierarchy or CLT+ALT+H

2)In jdeveloper it is -> right click on the method and select calls or ALT+SHIFT+H

Rohan Gala
  • 641
  • 5
  • 18
1

Yes, most modern IDE:s will let you either search for usages of a method or variable. Alternatively, you could use a debugger and set a trace point on the method entry, printing a stack trace or whatever every time the method is invoked. Finally, you could use some simple shell util to just grep for the method, such as

find . -name '*.java' -exec grep -H methodName {} ;

The only method that will let you find invokations made through some reflection method, though, would be using the debugger.

Christoffer
  • 12,712
  • 7
  • 37
  • 53
  • I use such *find* so often that I've got *fij* ("find in java) and *fix* (find in xml) and *fix* (find in text files) etc. aliases but... Such a *find* is not doing the same thing as an IDE: it shall also show you comments using that string and methods from other packages that happen to have the same name, which is a major PITA. So as much as I do use it once in a while it's not anywhere near as good as what an IDE does. – SyntaxT3rr0r Aug 28 '11 at 11:52
0

The closest that I could find was the method described in this StackOverflow questions selected answer.check this out

Community
  • 1
  • 1
Bobby
  • 18,217
  • 15
  • 74
  • 89
-1

You can do this with something in your IDE such as "Find Usages" (which is what it is called in Netbeans and JDeveloper). A couple of things to note:

  1. If your method implements a method from an interface or base class, you can only know that your method is POSSIBLY called.
  2. A lot of Java frameworks use Reflection to call your method (IE Spring, Hibernate, JSF, etc), so be careful of that.
  3. On the same note, your method could be called by some framework, reflectively or not, so again be careful.
GreenieMeanie
  • 3,560
  • 4
  • 34
  • 39