views:

35

answers:

2

I need to write a tool that lists the classes that call methods of specified interfaces. It will be used as part of the build process of a large java application consisting of many modules. The goal is to automatically document the dependencies between certain java modules.

I found several tools for dependency analysis, but they don't work on the method level, just for packages or jars. Finally I found ASM, that seems to do what I need.

The following code prints the method dependencies of all class files in a given directory:

import java.io.*;
import java.util.*;

import org.objectweb.asm.ClassReader;

public class Test {

    public static void main(String[] args) throws Exception {

        File dir = new File(args[0]);

        List<File> classFiles = new LinkedList<File>();
        findClassFiles(classFiles, dir);

        for (File classFile : classFiles) {
            InputStream input = new FileInputStream(classFile);
            new ClassReader(input).accept(new MyClassVisitor(), 0);
            input.close();
        }
    }

    private static void findClassFiles(List<File> list, File dir) {
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                findClassFiles(list, file);
            } else if (file.getName().endsWith(".class")) {
                list.add(file);
            }
        }
    }
}

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;

public class MyClassVisitor extends EmptyVisitor {

    private String className;

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

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {

        System.out.println(className + "." + name);
        return new MyMethodVisitor();
    }
}

import org.objectweb.asm.commons.EmptyVisitor;

public class MyMethodVisitor extends EmptyVisitor {

    @Override
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc) {

        String key = owner + "." + name;
        System.out.println("  " + key);
    }
}

The Problem:

The code works for regular classes only! If the class file contains an interface, visitMethod is called, but not visitMethodInsn. I don't get any info about the callers of interface methods.

Any ideas?

+1  A: 

I think this is because Interface methods do not have a method body. Try writing an empty method as part of a 'normal' class and see if visitMethodInsn is invoked.

By the way, have you considered using java.lang.instrument to discover the classes that are loaded at runtime and do your instrumentation that way, rather than reading the class files from disk?

Amir Afghani
I don't think that having a method body is relevant in this case. The interface method is visited because *visitMethod()* is called and a new *MyMethodVisitor* instance is returned, but this *MyMethodVisitor* seems to be ignored afterwards.I read the classes from disk, because I need to analyze an application that is not running.
Olaf Mertens
A: 

I have to admit, I was confused...

I thought that asm visitors do some magic to give me the list of all callers of a given method, like a stacktrace. Instead they justs parse classes and method bodies. Fortunatly, this is totally sufficent for my needs as I can build the call tree by myself.

The following code lists all methods that are called by other methods, checking class files in given directory (and subdirectories) only:


import java.io.*;
import java.util.*;

import org.objectweb.asm.ClassReader;

public class Test {

    public static void main(String[] args) throws Exception {

        File dir = new File(args[0]);

        Map<String, Set<String>> callMap = new HashMap<String, Set<String>>();

        List<File> classFiles = new LinkedList<File>();
        findClassFiles(classFiles, dir);

        for (File classFile : classFiles) {
            InputStream input = new FileInputStream(classFile);
            new ClassReader(input).accept(new MyClassVisitor(callMap), 0);
            input.close();
        }

        for (Map.Entry<String, Set<String>> entry : callMap.entrySet()) {
            String method = entry.getKey();
            Set<String> callers = entry.getValue();

            if (callers != null && !callers.isEmpty()) {
                System.out.println(method);
                for (String caller : callers) {
                    System.out.println("    " + caller);
                }
            }
        }
    }

    private static void findClassFiles(List<File> list, File dir) {
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                findClassFiles(list, file);
            } else if (file.getName().endsWith(".class")) {
                list.add(file);
            }
        }
    }
}

import java.util.*;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;

public class MyClassVisitor extends EmptyVisitor {

    private String className;
    private Map<String, Set<String>> callMap;

    public MyClassVisitor(Map<String, Set<String>> callMap) {
        this.callMap = callMap;
    }

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

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {

        return new MyMethodVisitor(className + "." + name, callMap);
    }
}

import java.util.*;

import org.objectweb.asm.commons.EmptyVisitor;

public class MyMethodVisitor extends EmptyVisitor {

    private String currentMethod;
    private Map<String, Set<String>> callMap;

    public MyMethodVisitor(String currentMethod,
            Map<String, Set<String>> callMap) {
        this.currentMethod = currentMethod;
        this.callMap = callMap;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc) {

        String calledMethod = owner + "." + name;

        Set<String> callers = callMap.get(calledMethod);
        if (callers == null) {
            callers = new TreeSet<String>();
            callMap.put(calledMethod, callers);
        }

        callers.add(currentMethod);
    }
}
Olaf Mertens