Using ASM, you can in theory trace for each method if another method of the same class is invoked from within it. The visitor API's method that is responsible for defining method invocations is visitMethodIns
. Assuming that your class was called bar.Foo
, you would need to trace:
visitMethodIns(<any>, "bar.Foo", <any>, <any>)
You would then need to build a transitive relation of methods calling each other where the last two parameters allow you to build such a relation hierarchy. Additionally, you would need to trace the arguments of these method invocations, what is more tricky but not impossible either.
The reason it is more complex is the number of possible ways an argument can be loaded onto the operand stack. For your example, you only need to pay attention to the visitIns and the visitLCDIns callbacks.
When calling a method on a constant pool value (LCD), the resolution of the argument is rather trivial. You would however need to trace the entire instruction chain before calling a method to learn of the local variable assignment in order to know that you are calling the method on the method parameter. Thus, you could find out that
ALOAD_0 / ASTORE_1 / ALOAD_1 => ALOAD_0
is an effective result of a sequence of reads/writes form the methods local variable array.
With all this, from parsing the byte code, you would learn about the following call-transitions:
m1(Ljava/lang/Object)V -> m2(Ljava/lang/Object)V [ALOAD 0]
-> m2(Ljava/lang/Object)V [LCD "box"]
m2(Ljava/lang/Object)V -> m3(Ljava/lang/Object)V [ALOAD 0]
You could then use these results to parse your block where you find out about these method calls and their implications. You would however have created a quite fragile solution where indirections such as:
{
Foo foo = this;
foo.m1("bar");
}
would not be discovered. As pointed out in the comments, you basically need to emulate the Java virtual machine in order to "run" your code.
And even if you implement a complex solution to trace all this, you could still not be sure of your result. What happens when I invoke an interface method from within an implementation. Or a method of a subclass? Thanks to the dynamic dispatch of methods, you can never be sure of the target that is called.