开发者

Can ASM method-visitors be used with interfaces?

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 MyMethodVisi开发者_JS百科tor 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?


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?


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


Interesting, someone downvoted my answer without making any comments to improve my answer, I don't think my answer is wrong, it describes why ASM doesn't go into interface method.

public interface FF {

    default void hh(String name) {

    }

    String hello(String hello);
}

javap executtion result ,like below , interface method body no message , like LineNumberTable,LocalVariableTable, so can't be parse by asm

➜  ~ javap -v  /Users/chenshun/tool/winter/target/test-classes/io/github/chenshun00/web/asm/FF.class
Classfile /Users/chenshun/tool/winter/target/test-classes/io/github/chenshun00/web/asm/FF.class
  Last modified 2022-7-15; size 384 bytes
  MD5 checksum de24290be11cb7706f83d3a355261367
  Compiled from "FF.java"
public interface io.github.chenshun00.web.asm.FF
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #16            // io/github/chenshun00/web/asm/FF
   #2 = Class              #17            // java/lang/Object
   #3 = Utf8               hh
   #4 = Utf8               (Ljava/lang/String;)V
   #5 = Utf8               Code
   #6 = Utf8               LineNumberTable
   #7 = Utf8               LocalVariableTable
   #8 = Utf8               this
   #9 = Utf8               Lio/github/chenshun00/web/asm/FF;
  #10 = Utf8               name
  #11 = Utf8               Ljava/lang/String;
  #12 = Utf8               hello
  #13 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #14 = Utf8               SourceFile
  #15 = Utf8               FF.java
  #16 = Utf8               io/github/chenshun00/web/asm/FF
  #17 = Utf8               java/lang/Object
{
  public void hh(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 17: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lio/github/chenshun00/web/asm/FF;
            0       1     1  name   Ljava/lang/String;

  public abstract java.lang.String hello(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "FF.java"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜