2

I try to analyze Java source code in Java6. ANTLR4 does great job in lexical analysis indeed but now I wonder if more advanced problems, such as listing inherited methods, can be managed.

My idea (according to some googling):

  1. load source files
  2. get system compiler by ToolProvider.getSystemJavaCompiler() and compile sources via run() into .class files
  3. [maybe?] pack .class files to .jar and load them somehow
  4. use Java reflection to analyze compiled classes
  5. delete .class files
  6. write output (e.g. inherited methods/fields etc.)

Bullets 1, 4, 5 and 6 are clean enough and well described. I belive nr. 2 can be solved using this tutorial. So my core problem is nr. 3 as I can't figure out how to load .class files and analyse them.

Any ideas? Is this even possible? If so, how? Could you recommend me either a tutorial or examples? Is my idea even correct?

I prefer not using third-party libraries as I'd like to understand in depth.

Roman C
  • 49,761
  • 33
  • 66
  • 176
petrbel
  • 2,428
  • 5
  • 29
  • 49
  • The class file format is well-defined. IMO doing this manually is a pretty awful idea, though; I understand wanting to learn in-depth, but that's ancillary to doing byte code analysis. – Dave Newton Jun 30 '14 at 13:13
  • @DaveNewton Couldn't I just extend (somehow) classpath to compiled `.class` files and then analyse them using reflection? IMHO all I need is to access compiled classes for `Class.forName(...)`, isn't it? – petrbel Jun 30 '14 at 13:16
  • How will you know what to reflect on? – Dave Newton Jun 30 '14 at 13:21
  • Lexical analysis provided by ANTLR gives AST including class names etc. So I could call `Class.forName` with full class name including package. – petrbel Jun 30 '14 at 13:27
  • Oh, you want to combine source with byte code analysis? Weird, and brittle, but OK. – Dave Newton Jun 30 '14 at 13:28
  • I need to use ANTLR for source code analysis but it can't decide whether primary literal given is **class** or **field**. And because of field inheritance, I can't decide myself either. So, is it possible to load compiled classes during runtime and then use reflexion? – petrbel Jun 30 '14 at 13:38
  • "I *need* to use ANTLR"? Why can't you consider other alternatives? – Ira Baxter Jun 30 '14 at 14:20
  • You have lots of detail except in step 4, where you need it. What specific analysis do you want to do? That question should drive everything. What makes you think the analysis you want to do, can be done by reflection? – Ira Baxter Jun 30 '14 at 14:24
  • 1
    The usual answer to "How do I static analysis (having never read a compiler book)?" is "Go read a compiler book, then ask again". Google for my essay *Life After Parsing* if you want a pithier discussion. – Ira Baxter Jun 30 '14 at 14:26

1 Answers1

1

Thanks to all comments and google I finally figured it out - basically I needed an example like this:

/* Usage
--------
$ javac CompileJarLoadReflect.java
$ java CompileJarLoadReflect MyClass YourClass CompileJarLoadReflect
MyClass.java compilation is successful
YourClass.java compilation is successful
CompileJarLoadReflect.java compilation is successful
3 files successfully compiled
Class MyClass
    myMethod
Class YourClass
    yourMethod
Class CompileJarLoadReflect
    main
    compile
    compile
    load
    jar
*/

/* Thanks to
------------
http://www.java2s.com/Code/Java/File-Input-Output/CreateJarfile.htm
http://stackoverflow.com/questions/194698/how-to-load-a-jar-file-at-runtime/673414#673414
*/

import javax.tools.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;


/** Simple compilation, packaging and loading example */
public class CompileJarLoadReflect {
    /** JAR buffer size*/
    public static int BUFFER_SIZE = 10 * 1024;

    /** Compile all files given (by their location) */
    public void compile(String[] files) throws Exception {
        for (String f : files) compile(f + ".java");
        System.out.println(files.length + " files successfully compiled");
    }

    /** Compile one particular file */
    protected void compile(String f) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int compilationResult = compiler.run(null, null, null, f);

        if (compilationResult == 0) System.out.println(f + " compilation is successful");
        else throw new Exception("Compilation error at file " + f);
    }

    /** Pack tobeJared classes into jarName */
    public void jar(String jarName, String[] tobeJared) throws Exception {
        File archiveFile = new File(jarName);
        byte buffer[] = new byte[BUFFER_SIZE];

        FileOutputStream stream = new FileOutputStream(archiveFile);
        JarOutputStream out = new JarOutputStream(stream, new Manifest());

        for (String name : tobeJared) {
            File f = new File(name + ".class");
            if (f == null || !f.exists() || f.isDirectory()) throw new Exception("Jar problem at file " + name);

            JarEntry jarAdd = new JarEntry(f.getName());
            jarAdd.setTime(f.lastModified());
            out.putNextEntry(jarAdd);

            FileInputStream in = new FileInputStream(f);
            while (true) {
                int nRead = in.read(buffer, 0, buffer.length);
                if (nRead <= 0) break;
                out.write(buffer, 0, nRead);
            }
            in.close();
        }

        out.close();
        stream.close();
    }

    /** Load jar archive at jarName and then print methods of all classes in clazzes  */
    public void load(String jarName, String[] clazzes) throws Exception {
        File file  = new File(jarName);
        URL url = file.toURL();  
        URL[] urls = new URL[]{url};
        ClassLoader cl = new URLClassLoader(urls);

        for (String c : clazzes) {
            System.out.println("Class " + c);
            Class cls = cl.loadClass(c);
            Method[] methods = cls.getDeclaredMethods();
            for (Method m : methods) System.out.println("\t" + m.getName());
        }
    }

    /** Try everyting out, use params without .java */
    public static void main(String[] args) {
        String jarName = "output.jar";

        try {
            CompileJarLoadReflect cjlr = new CompileJarLoadReflect();
            cjlr.compile(args);
            cjlr.jar(jarName, args);
            cjlr.load(jarName, args);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
}

I hope it helps others.

petrbel
  • 2,428
  • 5
  • 29
  • 49