Можно ли использовать методы-посетители ASM с интерфейсами?

Мне нужно написать инструмент, который перечисляет классы, вызывающие методы указанных интерфейсов. Он будет использоваться как часть процесса сборки большого Java-приложения, состоящего из множества модулей. Цель состоит в том, чтобы автоматически документировать зависимости между определенными модулями Java.

Я нашел несколько инструментов для анализа зависимостей, но они не работают на уровне методов, только для пакетов или jar-файлов. Наконец я нашел ASM, который, похоже, делает то, что мне нужно.

Следующий код выводит зависимости методов всех файлов классов в данном каталоге:

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

Проблема:

Код работает только для обычных занятий! Если файл класса содержит интерфейс, вызывается visitMethod, но не visitMethodInsn. Я не получаю никакой информации о вызывающих сторонах методов интерфейса.

Есть идеи?


person Olaf Mertens    schedule 19.05.2010    source источник


Ответы (2)


Я думаю, это потому, что методы интерфейса не имеют тела метода. Попробуйте написать пустой метод как часть «нормального» класса и посмотрите, вызывается ли метод visitMethodInsn.

Кстати, не рассматривали ли вы возможность использования java.lang.instrument для обнаружения классов, которые загружаются во время выполнения, и выполнения таким образом инструментовки вместо чтения файлов классов с диска?

person Amir Afghani    schedule 19.05.2010
comment
Я не думаю, что наличие тела метода имеет значение в этом случае. Метод интерфейса посещается, потому что вызывается visitMethod() и возвращается новый экземпляр MyMethodVisitor, но этот MyMethodVisitor впоследствии игнорируется. Я читаю классы с диска, потому что мне нужно проанализировать приложение, которое не запущено. - person Olaf Mertens; 20.05.2010

Должен признаться, я был сбит с толку...

Я думал, что посетители asm делают что-то волшебное, чтобы предоставить мне список всех вызывающих данный метод, как трассировку стека. Вместо этого они просто анализируют классы и тела методов. К счастью, этого вполне достаточно для моих нужд, так как я могу построить дерево вызовов самостоятельно.

В следующем коде перечислены все методы, вызываемые другими методами, с проверкой файлов классов только в данном каталоге (и подкаталогах):


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);
    }
}
person Olaf Mertens    schedule 20.05.2010