Find out if one method can call another

I am trying to figure out how to take Java pojo, and analyze its method for all the other methods and functions that it could call. For example, here is an example of hard-coding output. How can I make this general? I need to analytically analyze Java objects to determine which methods they could call if they were executed. Example:

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); } } 
+5
source share
7 answers

You need to build a call graph, and then ask if the two nodes (the caller and the callee) are connected in the call graph. This is not an easy task.

What you need to do:

  • Parse the source code that makes up your application. Java parsers are relatively easy to find. Java 1.8 parsers are not so simple, but they are hidden there in the Java compiler, which you can use, and the other in Eclipse JDT; My company also provides our DMS Toolkit.
  • Create abstract syntax trees for them; you need code structures. The Java compiler, JDT, and DMS can do this.
  • Resolve the name and type. You need to know what the definition of each character means. The Java compiler definitely does this for one compilation unit at a time. JDT can do this for many files; I do not have much experience with this. DMS can do this for very large sets of Java source files at once.
  • Now you need to do a (object) point analysis: you want to know, for some (object-valued) field, which objects of a particular instance it can point to; which ultimately tells you what methods it can use to run. You will get information for this task by checking the AST and character table definitions that indicate what each character means. If you see Xf = new foo; you know that f in X can point to foo as a basic fact. Darts and erasing styles make it dirty. If you see Yg = Zh, you know that g in Y can point to everything that h in Z points to; Of course, Z can be a class that inherits from Z. If you see Yg = a [...], then you know that g in Y can point to any object that can be assigned to an array a. If you see Yg = bar (...), then you know that g in Y can point to everything that a bar can return; Unfortunately, you now need a call graph to answer the question narrowly. You can approach this in different ways to get a conservative answer. Now that you know how the values ​​are related to each other, you need to go through a transit closure on this set to get an idea that each g from each Y can point to. You can get a more accurate answer when you consider the control and data flow by individual methods, but these are more mechanisms for building. (For more information, see point analysis .) The Java compiler calculates some of this information when compiling, but not for the entire source file system; remember that it processes the source files one at a time. I don't think JDT is trying to do this at all. Our DMS does not do this yet, but we did it for 26 million line C code systems; this is possibly a more complicated problem because people do all kinds of offensive things with pointers, including the drops that are lying.
  • Finally, you can build a call graph. For each method, build a node call graph. For each call site in a method, define its set of called parties and connect the calling node to the called node. The previous step contains the information necessary to provide these links.

[Perhaps you can avoid the part of resolving parsing / determining the type of names above using Wala , which is essentially built by doing most of the above].

With the call graph, if you want to find out if A can call B, find the node for A in the call graph and see if there is a path to B.

Another note here means that this is a 6 month task for the compiler class. I think it is 6 months for an experienced compiler or more (and we did not consider unpleasant problems like class loaders and reflective calls).

I think you better find a solution for this that someone else has already built. Probably someone has; it is hardly easy to find, or she wants to part with him. You can find implementations performed at universities; There are all kinds of works written by scientists (and supported by the prototype) for computing object graphs. On the other hand, all these systems are prototypes and are built by small unpaid alumni teams, they usually do not handle all extreme cases, not to mention the latest version of Java (lambdas, anyone?)

+8
source

What you are trying to do is called static code analysis - in particular data flow analysis, but with a twist ... you didn’t show, you look at the source code, but at the compiled code ... if you want to do it at runtime, where you have to deal with compiled (bytecode) code instead of the source. So, you are looking for a library capable of analyzing a bytecode data stream. There are many libraries for help (now that you know what to look for, you can find alternatives to my recommendation if you want).

OK, before reaching the example ... I like javassist - I think this is as clear as a bytecode library with great examples and documentation on the Internet. javassit has some higher level bytecode parsing API , so you don’t even have to dig too deep, depending on what you need to do.

To print the output for your Foo / Bar example, use the following code:

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

will print:

 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 = [] 

If you need more details, you will really need to read the bytecode and the 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; } } } } 

Hope this helps you get started =)

+3
source

You can use the ASM api to find class file information. The sample code provides reliable information on how to get the detail method.

Analyzer class

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

Class bar

 package sample.code.analyze; public class Bar { public void gaz() { System.out.println("gaz"); } } 

Class 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(); } } } 
+2
source

It's quite complicated - you will need to use the Java Reflect API, as well as do heavy parsing and a lot of the work the compiler will do. Instead, you can simply use one of the many Java Dependency tools / plugins already available (e.g. JDepend from fooobar.com/questions/737767 / ... )

+1
source

OP Answer for reference:

The goal is to make this work:

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

Your own object dependency will be printed. For this you need:

Deep knowledge of the ASM Tree API:

http://asm.ow2.org/

Ways to open and access Jar content, including

 MethodInvocationGraph.class.getProtectionDomain().getCodeSource() 

A JNI Signature parser

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

And a graph structure such as

http://jgrapht.org/

+1
source

There is a problem in the method due to the static type. The static method will call Execute first at the start of the class time. Thus, all will be performed at the first stage and will not be able to call second-rate due to the static qualities of the method. So the main method cannot call the above method.

-1
source

I think you can get all the information from stacktrace if you call any method. When we get any exception, we can see the stack trace with printStackTrace (); method. This is not an answer, but it can help you find a solution to the problem.

-1
source

Source: https://habr.com/ru/post/1204964/


All Articles