Search code examples
javamethodsconstructorlocal-variablesbcel

java - How to retrieve anything inside method


From what i know, java cannot retrieve anything inside method. so i using option -g or -g:vars in javac.

for e.g :

class Test {
    int a=0;
    void method(boolean boo){
        String b;
        try
        {
            new Thread().sleep(1000);
        }
        catch(InterruptedException e){}
        JOptionPane.showMessageDialog(null,"test");
        BufferedImage image=ImageIO.read(new File("C:\\file.png"));
    }
}

So, i use BCEL to retrieve local variable.

import org.apache.bcel.classfile.*;
import org.apache.bcel.Repository;
class javap
{
    public static void main(String[]args)
    {
        try
        {
            JavaClass jc = Repository.lookupClass("test");
            ConstantPool constantPool = jc.getConstantPool();
            Method [] method=jc.getMethods();
            for (Method m : method) 
            {
                LocalVariableTable lvt=m.getLocalVariableTable();
                LocalVariable[] lv=lvt.getLocalVariableTable();
                for(LocalVariable l : lv)
                {   
                    System.out.println(l.getName()+" : "+l.getSignature());
                }
            }
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
    }
}

But it doesn't work if variable is not initialized like String b. Additionally I want to track constructor calls like new Thread() or new File() as well as invocations of static methods and inside intialize inside JFileChooser like new File and JOptionPane too. So I want to see in output Thread, String b, JOptionPane, ImageIO, and File.

What should I do, to make them are printed in my program?


Solution

  • You simply cannot get the b variable, because java compilers (at least javac and ecj) do not put it into the generated class file at all: if variable is not assigned, no variable slot is allocated and it's not stored in the LocalVariableTable. You can create unused variable with longer name like String blahblah;, compile the class, open the compiled .class-file in text editor and search for blahblah string. You will not found it. So BCEL cannot help you to find the variable which is absent.

    If you want to track new objects creation and static methods invocation, you can do it scanning the method bytecode. The easiest way to do it with BCEL is to utilize the MethodGen (even though you don't want to generate the new method). Here's the full code:

    import org.apache.bcel.Constants;
    import org.apache.bcel.Repository;
    import org.apache.bcel.classfile.ConstantMethodref;
    import org.apache.bcel.classfile.ConstantPool;
    import org.apache.bcel.classfile.JavaClass;
    import org.apache.bcel.classfile.LocalVariable;
    import org.apache.bcel.classfile.LocalVariableTable;
    import org.apache.bcel.classfile.Method;
    import org.apache.bcel.generic.ConstantPoolGen;
    import org.apache.bcel.generic.INVOKESTATIC;
    import org.apache.bcel.generic.InstructionHandle;
    import org.apache.bcel.generic.MethodGen;
    import org.apache.bcel.generic.NEW;
    
    class javap
    {
        public static void main(String[]args)
        {
            try
            {
                JavaClass jc = Repository.lookupClass("Test");
                ConstantPool constantPool = jc.getConstantPool();
                Method [] method=jc.getMethods();
                for (Method m : method) 
                {
                    LocalVariableTable lvt=m.getLocalVariableTable();
                    LocalVariable[] lv=lvt.getLocalVariableTable();
                    for(LocalVariable l : lv)
                    {   
                        System.out.println(l.getName()+" : "+l.getSignature());
                    }
                }
                ConstantPoolGen cpg = new ConstantPoolGen(constantPool);
                for(Method m : method)
                {
                    MethodGen mg = new MethodGen(m, m.getName(), cpg);
                    for(InstructionHandle ih = mg.getInstructionList().getStart(); 
                            ih != null; ih = ih.getNext())
                    {
                        if(ih.getInstruction() instanceof NEW) 
                        {
                            NEW newInst = ((NEW)ih.getInstruction());
                            String className = constantPool.getConstantString(
                                newInst.getIndex(), Constants.CONSTANT_Class);
                            System.out.println("Class instantiation: "+className);
                        }
                        if(ih.getInstruction() instanceof INVOKESTATIC) 
                        {
                            INVOKESTATIC newInst = ((INVOKESTATIC)ih.getInstruction());
                            String className = constantPool.getConstantString(
                                    ((ConstantMethodref) constantPool
                                            .getConstant(newInst.getIndex()))
                                            .getClassIndex(),
                                    Constants.CONSTANT_Class);
                            System.out.println("Static call: "+className);
                        }
                    }
                }
            }
            catch(Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }
    

    The output is the following:

    this : LTest;
    this : LTest;
    boo : Z
    Class instantiation: java/lang/Thread
    Static call: java/lang/Thread
    Static call: javax/swing/JOptionPane
    Class instantiation: java/io/File
    Static call: javax/imageio/ImageIO
    

    Note that you have java/lang/Thread twice, because new Thread() is catched as object creation and Thread.sleep() is catched as static method invocation.