2014-10-18 150 views
5

我想弄清楚如何使用Java POJO,并分析其所有其他可以调用的方法和函数的方法。例如,以下是输出的硬编码示例。我怎样才能使这个一般?我需要以编程方式分析Java对象以确定如果执行它们可以调用哪些方法。例如:找出是否有一种方法可以调用另一个方法

package com.example.analyze; 

public class Main 
{ 

    private static class Foo { 

     public void foo(int value, Bar bar) { 
      if(value > 5) 
       bar.gaz(); 
     } 
    } 

    private static class Bar { 

     public void gaz() { 
      System.out.println("gaz"); 
     } 
    } 

    private static class Analyzer { 

     public void analyze(Object object){ 
      System.out.println("Object method foo could call Bar method gaz"); 
     } 

    } 

    public static void main(String[] args) 
    { 
     Foo foo = new Foo(); 
     Analyzer analyzer = new Analyzer(); 
     analyzer.analyze(foo); 
    } 
} 
+2

http://depfind.sourceforge.net/ – Jayan 2014-10-18 05:26:07

+0

谢谢,如果您提供了如何完成示例的示例,我很乐意接受。 – 2014-10-18 23:19:17

+0

@Jayan,看看这个pastebin。如何将$ 1连接到函数doSomething?在注释部分是打印jdpends出站链接的3个级别的结果http://pastebin.com/b9E4zEdg – 2014-10-19 16:21:02

回答

8

你需要的是构造一个调用图,然后询问在调用图中是否连接了两个节点(一个调用者和被调用者)。这不是一件容易的事。

你需要做的:

  • 解析源代码,使您的应用程序。 Java解析器相对容易找到。 Java 1.8分析器不是那么容易,但是您可以使用Java编译器中的一个隐藏,另一个隐藏在Eclipse JDT中;我的公司还提供了我们的DMS工具包。
  • 构建相同的抽象语法树;你需要代码结构。 Java编译器,JDT和DMS都可以做到这一点。
  • 执行名称和类型解析。你需要知道每个符号的定义是什么意思。 Java编译器一次只对一个编译单元执行此操作。 JDT可能会为许多文件执行此操作;我对此没有太多的经验。 DMS可以一次完成大量的Java源文件。
  • 现在你需要做一个(对象)指向分析:你想知道,对于任何(对象值)字段,它可能指向哪个特定的实例对象;最终会告诉你可能用什么方法触发。您将通过检查AST和符号表定义来获取有关此任务的信息,以说明每个符号的含义。如果你看到X.f = new foo;作为一个基本事实,你知道X中的f可以指向foo。泛型和类型擦除使这个混乱。如果你看到Y.g = Z.h,你知道Y中的g可以指向任何在Z中可以指向的h;当然Z可能是继承自Z的类。如果你看到Y.g = a [...],那么你知道Y中的g可以指向任何可能已经分配给数组a的对象。如果你看到Y.g = bar(...),那么你知道Y中的g可以指向酒吧可能会返回的任何东西;不幸的是,你现在需要一个调用图来回答这个问题。你可以通过各种方式来近似这个来得到一个保守的答案。现在你已经知道价值是如何相互关联的,你必须对这个集合进行传递闭包,以便了解每个Y中每个g能指向什么。如果考虑到单个方法的控制和数据流,你可以得到更准确的答案,但这是更多的机制。 (以下是有关points-to analysis的更多详细信息。)Java编译器在编译时会计算一些信息,但不会计算整个系统的源文件;请记住它一次处理一个源文件。我不认为JDT试图这样做。我们的DMS尚未做到这一点,但我们已经为2600万行C代码系统做了这些工作;这可能是一个更难的问题,因为人们会用包括演员在内的指针来做各种各样的辱骂事情。
  • 最后你可以构造一个调用图。对于每种方法,构建一个调用图节点。对于方法中的每个呼叫站点,确定它的一组被调用者并将呼叫节点链接到被叫节点。上一步收集了提供这些链接所需的信息。

[您可能能够避免上述使用Wala的解析/名称类型解析部分,其基本上通过执行上述大部分]。

通过调用图,如果你想知道一个可以调用B,发现在调用图对于A节点,并查看是否有到B

路径另注在这里表明,这是编译器类的6个月任务。我认为对于一个经验丰富的编译器人员来说,这是6个月,或者更多(并且我们没有解决类加载器和反射调用等讨厌的问题)。

我认为你最好找到一个解决方案,其他人已经建成。可能有人有;不太可能很容易找到它,或者她想要分享它。您可能会在Univerisities中找到实现;有学者撰写的各种论文(并由原型支持)来计算对象图。不利的一面是所有那些原型,并正在由毕业生小,无偿团队构建,他们通常不处理所有的边缘情况更不用说了最新版本的Java系统(lambda表达式,任何人吗?)

+0

你能提供这些点的例子吗? – 2014-10-26 04:14:25

+0

所以我写的解决方案很像这样。基本上解析字节码,寻找'调用*'调用,并添加一个节点并将边缘定向到一个图形结构。然后,方法依赖关系是在其出站链接上的节点上进行深度优先搜索。 Steve下面的答案使用'javassist',我认为一个完整的答案是一起的。现在我正在修改原型来使用ASM而不是JavaP,如果你对这个问题有任何想法... http://stackoverflow.com/questions/26575111/java-asm-how-to-get-opcode -name-and-tagvalue-from-asm-insnnode – 2014-10-27 03:17:11

+0

@DavidWilliams:你的图似乎是instance-method-M调用abstract-method-x。想象一下,我有X类,它有一个(可能是抽象的)方法x,而类X1和类X2都从X继承,方法x'和x“覆盖x。您构建图形的方式看起来似乎只知道该方法可以调用* some * x,但不具体指x,x'或x“'。这就是你真正想要的通话图吗?如果您需要更多信息,您必须知道*在呼叫站点使用X,X1或X2中的哪一个;这就是为什么我说你需要“指向”分析。 – 2014-10-27 03:45:41

1

这是非常艰难的 - 您将需要使用Java Reflect API并执行一些繁重的解析和编译器会执行的大量工作。相反,您可以使用已有的许多Java Dependency工具/插件之一(如来自https://stackoverflow.com/a/2366872/986160的JDepend)

+0

我对Reflection api很熟悉。你认为解析会带来什么?在pojo的记忆中没有办法做到这一点吗? – 2014-10-18 04:14:30

+1

它将涉及解析所有的方法体并找到方法调用(使用正则表达式和语法树)。您将需要跟踪变量以及它们是什么类型,以便您可以将依赖关系记录到这些类类型。您可能需要对所有文件进行多次传递。您还需要构建符号树和语法树,并在完成之后构建依赖关系图。 但正如我所说,这可能是一个在Compilers课程中的六个月课程项目。 – 2014-10-18 04:20:32

+0

如何分析像javap这样的字节码? – 2014-10-18 04:56:01

-1

由于采用静态类型,因此存在方法问题。 静态方法将在类的开始时调用第一个Execute在第一个阶段所有将执行并且由于方法的静态属性将不能再次调用。所以 主要方法将无法调用上述方法。

+0

OP试图找到方法调用依赖关系,不理解什么是允许的,什么是不使用静态的。 – 2014-10-21 15:30:55

3

你想要做什么叫static code analysis - 特别是数据流分析,但有一个转折......你没有表明你在看源代码,但在编译代码......如果你想在运行时做到这一点,在哪里你不得不处理编译的(字节码)代码而不是源代码。因此,您正在寻找一个能够进行字节码数据流分析的库。有很多图书馆可以帮助你(现在你知道要搜索什么,如果你愿意的话,你可以找到替代我的建议)。

好的,没有得到一个例子...我喜欢javassist - 我发现它像一个字节码库一样清晰,可以通过很好的示例和文档在线。 javassit有一些更高层次的bytecode analysis API,所以你可能甚至不必深入挖掘,这取决于你需要做什么。

要为您美孚/酒吧例如打印输出上面,使用下面的代码:

public static void main (String... args) throws Exception { 
    Analyzer a = new Analyzer(); 

    ClassPool pool = ClassPool.getDefault(); 
    CtClass cc = pool.get("test.Foo"); 
    for (CtMethod cm : cc.getDeclaredMethods()) { 
     Frame[] frames = a.analyze(cm); 
     for (Frame f : frames) { 
      System.out.println(f); 
     } 
    } 
} 

会打印:

locals = [test.Foo, int, test.Bar] stack = [] 
locals = [test.Foo, int, test.Bar] stack = [int] 
locals = [test.Foo, int, test.Bar] stack = [int, int] 
null 
null 
locals = [test.Foo, int, test.Bar] stack = [] 
locals = [test.Foo, int, test.Bar] stack = [test.Bar] 
null 
null 
locals = [test.Foo, int, test.Bar] stack = [] 

如果您需要更多的细节,你需要真正阅读字节码,并有JVM specification得心应手:

public static void main (String... args) throws Exception { 
     ClassPool pool = ClassPool.getDefault(); 
     CtClass cc = pool.get("test.Foo"); 
     for (CtMethod cm : cc.getDeclaredMethods()) { 
      MethodInfo mi = cm.getMethodInfo(); 
      CodeAttribute ca = mi.getCodeAttribute(); 
      CodeIterator ci = ca.iterator(); 
      while (ci.hasNext()) { 
       int index = ci.next(); 
       int op = ci.byteAt(index); 
       switch (op) { 
        case Opcode.INVOKEVIRTUAL: 
         System.out.println("virutal"); 
         //lookup in the JVM spec how to extract the actual method 
         //call info here 
         break; 
       } 
      } 
     } 
    } 

我希望这可以帮助你开始=)

+0

这是否解析类文件?我将如何在罐子上运行它? – 2014-10-26 04:16:04

+0

感谢您的回答,我将尝试使用javassist。目前我正在尝试ASM来解析字节码。 – 2014-10-27 03:18:09

+0

嗯,抱歉,延迟...我没有收到电子邮件通知。我写的例子假设有问题的类已经加载到类路径中以匹配你的例子 - 但是看起来你已经移过那个问题已经=) – 2014-10-29 00:45:51

-1

我想你可以从stacktrace获得所有信息,如果你调用任何方法。当我们得到任何异常时,我们可以使用printStackTrace()看到堆栈跟踪;方法。这不是一个答案,但它可以帮助您为您的问题找到解决方案。

+0

OP想知道一个方法*是否可以调用另一个方法。如果在正确的时刻发生堆栈跟踪,堆栈跟踪至多会提供它*确实*的意外证据。海报是正确的:这是*不是*答案。 – 2014-10-28 10:36:23

1

参考OP答:

的目标是得到这个工作:

MethodInvocationGraph methodInvocationGraph = 
     new MethodInvocationGraph(
      Disassembler.disassembleThisJar()); 

    methodInvocationGraph.printObjectMethodDependencyTree(methodInvocationGraph); 

,它将打印对象自己的依赖。要做到这一点,你需要:

在ASM树API的深入了解:

开放

http://asm.ow2.org/

方法和访问罐内容,包括

MethodInvocationGraph.class.getProtectionDomain().getCodeSource() 

JNI的签名解析器

http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/method.html

和图形框架如

http://jgrapht.org/

2

您可以使用ASM API来找到一个类文件的信息,示例代码给出了如何获得方法的细节清晰的概念。

分析器类

package sample.code.analyze; 

import java.io.IOException; 

import org.objectweb.asm.ClassReader; 
import org.objectweb.asm.ClassVisitor; 
import org.objectweb.asm.MethodVisitor; 
import org.objectweb.asm.Opcodes; 

public class Analyzer { 
    public void analyze(Object object) { 
     ClassVisitor cv = new ClassVisitor(Opcodes.ASM4) { 
      @Override 
      public MethodVisitor visitMethod(int access, String name, 
        String desc, String signature, String[] exceptions) { 

       System.out.println("Method: " + name + " -- " + desc); 
       return new MethodVisitor(Opcodes.ASM4) { 
        @Override 
        public void visitMethodInsn(int opcode, String owner, 
          String name, String desc, boolean arg4) { 
         System.out.println("-- opcode -- " + opcode 
           + " -- owner -- " + owner + "name -- " 
           + name + "desc -- " + desc); 
         super.visitMethodInsn(opcode, owner, name, desc, arg4); 
        } 
       }; 
      } 
     }; 
     try { 
      ClassReader classReader = new ClassReader(object.getClass().getCanonicalName()); 
      classReader.accept(cv, 0); 
     } catch (IOException e) { 
      System.err.println("Something went wrong !! " + e.getMessage()); 
     } 
    } 

    public static void main(String[] args) { 
     Foo foo = new Foo(); 
     Analyzer analyzer = new Analyzer(); 
     analyzer.analyze(foo); 
    } 
} 

酒吧类

package sample.code.analyze; 

    public class Bar { 
     public void gaz() { 
      System.out.println("gaz"); 
     } 
    } 

Foo类

package sample.code.analyze; 

import sample.code.analyze.Bar; 

public class Foo { 
    public void foo(int value, Bar bar) { 
     if (value > 5) { 
      bar.gaz(); 
     } 
    } 
} 
相关问题