Search code examples
javaencodingcompiler-constructioninternal

Internal compiling with proper encoding?


I have a program that writes a string to a .java file, uses javax.tools to compile that .java file into a .class file, and I then use a modified ClassLoader to attempt to get a Runnable instance of the class (by turning it into a byte array and using ClassLoader.defineClass to get an instance). But.., I'm having a problem. When the program attempts to get an instance of the class, it notices that it was not compiled the right way. I get a ClassFormatError that reads Incompatible magic value 1885430635 in class file <Unknown>. Here is my (fairly sloppy, at the moment) code:

import java.io.*;
import java.security.SecureClassLoader;

public class EnhancedClassLoader extends SecureClassLoader
{
    public Object createObjectFromFile(String fileName) throws 
    InstantiationException, IOException, IllegalAccessException
    {
        File file = new File(fileName);
        definePackage("compClassPack", null, null, null, null, null, null, file.toURI().toURL());
        byte[] classBytes = null;
        //ReadFileToByteArray
        {
            FileInputStream fis = new FileInputStream(file);
            int size = (int)file.length();
            classBytes = new byte[size];
            int offset = 0;
            int readed;
            while (offset < size && (readed = fis.read(classBytes, offset, size - offset)) != -1)
            {
                offset += readed;
            }
        fis.close();
        }
        Class<?> clazz = defineClass(null, classBytes, 0, classBytes.length);
            //The error is thrown here! ^^^

        return clazz.newInstance();
    }
}

.

import java.io.*;
import javax.tools.*;

public class Main
{
    public static void main(String[] args)throws Exception
    {
        File file = new File("Sample.java");
        FileWriter fw = new FileWriter(file);
        BufferedWriter bw = new BufferedWriter(fw);

        String code =
                "package compClassPack;\n" +
                "public class Sample implements Runnable\n" +
                "{\n" +
                "    public Sample(){}\n\n" +
                "    public static void main(String[] args)\n" +
                "    {\n" +
                "        new Sample().run();\n" +
                "    }\n\n" +
                "    public void run()\n" +
                "    {\n" +
                "        System.out.println(\"It worked! :D\");\n" +
                "    }\n" +
                "}";

        for(byte ch : code.getBytes())
        {
            if((char)ch!='\n')
                bw.write((char)ch);
            else
                bw.newLine();
        }
        bw.flush();
        bw.close();

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask ct = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
        ct.call();

        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics())
            System.out.format("Error on line %d in %d%n",
                    diagnostic.getLineNumber(),
                    ((FileObject)diagnostic.getSource()).toUri());

        fileManager.close();

        EnhancedClassLoader loader = new EnhancedClassLoader();
        Runnable runs = (Runnable) loader.createObjectFromFile(file.getAbsolutePath());
        runs.run(); 
    }
}

Solution

  • Your byte array is not valid bytecode. If it were, it would begin with the magic string (hex) 0xCAFEBABE. Instead, your first bytes are 1885430635 (decimal), which is hex 0x7061636b, which read one byte at a time gives the ASCII characters pack, which is just the beginning of the package declaration.

    In other words, you are trying to load a text file as if it were bytecode, and obviously Java can't construct any class from such a byte stream.