找出一种方法是否可以调用另一种方法

我试图找出如何获取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); } } 

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

你需要做什么:

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

[您可以使用Wala来避免上面的解析/名称类型解析部分, Wala基本上是通过执行上述大部分工作构建的]。

使用调用图,如果您想知道A是否可以调用B,请在调用图中找到A的节点,并查看是否有到B的路径。

这里的另一个注意事项表明这是编译器类的6个月任务。 我认为对于经验丰富的编译人员来说,这是6个月,或者更多(并且我们没有解决诸如类加载器和reflection调用之类的令人讨厌的问题)。

我认为你最好找到一个解决方案,其他人已经建立了。 有人可能; 不太可能很容易找到它或她想要分开它。 您可能会发现在Univerisities中完成的实现; 学术界编写了各种论文(并由原型支持)来计算对象图。 缺点是所有这些系统都是原型,并由小型,无偿的gradle生团队构建,他们通常不处理所有边缘情况,更不用说最新版本的Java(lambdas,任何人?)

你试图做的是静态代码分析 – 特别是数据流分析,但有一个扭曲…你没有表明你正在看源代码,但在编译代码…如果你想在运行时这样做,你必须处理编译(字节码)代码而不是源代码。 因此,您正在寻找能够进行字节码数据流分析的库。 有很多图书馆可以提供帮助(现在您知道要搜索什么,如果您愿意,可以找到我建议的替代方案)。

好的,没有得到一个例子……我喜欢javassist – 我发现它很清楚,因为字节码库可以在线提供很好的示例和文档。 javassit有一些更高级别的字节码分析API ,因此您甚至可能不需要深入挖掘,具体取决于您需要做什么。

要打印上述Foo / Bar示例的输出,请使用以下代码:

 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规范放在手边:

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

我希望这有助于你开始=)

您可以使用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(); } } } 

这非常困难 – 您需要使用Java Reflect API并进行一些繁重的解析和编译器可以完成的大量工作。 相反,您可以使用已经可用的许多Java依赖工具/插件之一(例如来自https://stackoverflow.com/a/2366872/986160的 JDepend)

OP答案供参考:

目标是让这个工作:

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

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

深入了解ASM Tree API:

http://asm.ow2.org/

打开和访问Jar内容的方法,包括

 MethodInvocationGraph.class.getProtectionDomain().getCodeSource() 

JNI签名解析器

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

和图形框架如

http://jgrapht.org/

由于静态类型的原因,方法存在问题。 静态方法将在类的开始时间调用第一个执行因此,所有将在第一阶段执行,并且由于方法的静态质量而无法进行第二次调用。 所以main方法无法调用上面的方法。

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