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

时间:2014-10-18 03:43:48

标签: java static-analysis

我试图弄清楚如何使用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);
    }
}

7 个答案:

答案 0 :(得分: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中可以指向的任何东西;当然Z可能是从Z继承的类。如果你看到Y.g = a [...],那么你知道Y中的g可以指向任何可能已经分配给数组a的对象。如果您看到Y.g = bar(...),那么您知道Y中的g可以指向条可能返回的任何内容;不幸的是,你现在需要一个调用图来狭隘地回答这个问题。您可以通过各种方式对此进行近似,以获得保守的答案。现在你已经知道了值是如何相互关联的,你必须对这个集合进行传递闭包,以便了解每个Y中每个g可以指向的内容。如果考虑到各个方法的控制和数据流,您可以获得更精确的答案,但这需要更多的机制来构建。 (以下是points-to analysis的更多详细信息。)Java编译器在编译时计算其中的一些信息,但不是整个源文件系统;记住它一次处理一个源文件。我不认为JDT会尝试这样做。我们的DMS还没有(还)做到这一点,但我们已经为C代码为2600万行的系统做到了这一点。这可能是一个更难的问题,因为人们用各种各样的指针来做各种各样的辱骂,包括演员谎言。
  • 最后,您可以构建一个调用图。对于每个方法,构造一个调用图节点。对于方法中的每个调用站点,确定其callees集并将调用节点链接到被调用节点。上一步收集了提供这些链接所需的信息。

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

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

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

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

答案 1 :(得分:3)

您尝试做的事情被称为static code analysis - 特别是数据流分析,但有一点扭曲......您没有表明您正在查看源代码,但是在编译代码中...如果你想在运行时这样做,你必须处理编译(字节码)代码而不是源代码。因此,您正在寻找能够进行字节码数据流分析的库。有很多图书馆可以提供帮助(现在您知道要搜索什么,如果您愿意,可以找到我推荐的替代方案)。

好的,没有得到一个例子......我喜欢javassist - 我发现它很清楚,因为字节码库可以在线提供很好的示例和文档。 javassit有一些更高级别bytecode analysis 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 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;
                }
            }
        }
    }

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

答案 2 :(得分:3)

您可以使用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 Class

package sample.code.analyze;

import sample.code.analyze.Bar;

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

OP答案供参考:

目标是让它发挥作用:

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

    methodInvocationGraph.printObjectMethodDependencyTree(methodInvocationGraph);

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

深入了解ASM树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/

答案 5 :(得分:-1)

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

答案 6 :(得分:-1)

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