動態編譯Java代碼

Java使得在運行時編譯Java代碼成爲可能…任何Java代碼!

編譯的入口點是ToolProvider類。它的Javadoc如下:

Provides methods for locating tool providers, for example, providers of compilers. This class complements the functionality of ServiceLoader.

翻譯一下就是,提供用於定位工具提供者(例如,編譯器的提供者)的方法。此類補充了ServiceLoader的功能。

從10年前發佈的1.6版開始,此類就可以在Java中使用,但似乎已被很大程度上忽略了。

代碼

示例代碼片段:

public class EvilExecutor {
    private String readCode(String sourcePath) throws FileNotFoundException {
        InputStream stream = new FileInputStream(sourcePath);
        String separator = System.getProperty("line.separator");
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        return reader.lines().collect(Collectors.joining(separator));
    }
    private Path saveSource(String source) throws IOException {
        String tmpProperty = System.getProperty("java.io.tmpdir");
        Path sourcePath = Paths.get(tmpProperty, "Harmless.java");
        Files.write(sourcePath, source.getBytes(UTF_8));
        return sourcePath;
    }
    private Path compileSource(Path javaFile) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        compiler.run(null, null, null, javaFile.toFile().getAbsolutePath());
        return javaFile.getParent().resolve("Harmless.class");
    }
    private void runClass(Path javaClass)
            throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URL classUrl = javaClass.getParent().toFile().toURI().toURL();
        URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{classUrl});
        Class<?> clazz = Class.forName("Harmless", true, classLoader);
        clazz.newInstance();
    }
    public void doEvil(String sourcePath) throws Exception {
        String source = readCode(sourcePath);
        Path javaFile = saveSource(source);
        Path classFile = compileSource(javaFile);
        runClass(classFile);
    }
    public static void main(String... args) throws Exception {
        new EvilExecutor().doEvil(args[0]);
    }
}

解釋:

  • readCode():從文件系統上的任意文件讀取源代碼,並將其作爲字符串返回。另一種實現方式是從整個網絡獲取源。
  • saveSource():根據源代碼在已啓用讀取的目錄中創建一個新文件。該文件名是硬編碼的,更完善的版本將解析code參數以創建一個根據其包含的類名命名的文件。
  • compileSource():從Java文件中編譯類文件。
  • runClass:加載已編譯的類並實例化一個新對象。爲了獨立於任何強制類型轉換,應在外部源代碼類的構造函數中設置要執行的代碼。

問題

從功能的角度來看,與不提供此功能的其他語言相比,即時編譯代碼可提高Java語言的價值。從安全角度來看,這是一場噩夢! 能夠在生產中執行任意代碼的想法應該使任何IT組織(包括開發人員)(如果不是大多數)中的任何人都感到不寒而慄。

經驗豐富的開發人員/操作人員或普通讀者可能還記得Java安全管理器以及如何激活它:

java -Djava.security.manager -cp target/classes ch.frankel.blog.runtimecompile.EvilExecutor harmless.txt

執行上述命令行將產生以下結果:

java.lang.SecurityManager@4e25154f
Exception in thread "main" java.security.AccessControlException:
    access denied ("java.io.FilePermission" "harmless.txt" "read")
  at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
  at java.security.AccessController.checkPermission(AccessController.java:884)
  at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
  at java.lang.SecurityManager.checkRead(SecurityManager.java:888)
  at java.io.FileInputStream.<init>(FileInputStream.java:127)
  at java.io.FileInputStream.<init>(FileInputStream.java:93)
  at ch.frankel.blog.runtimecompile.EvilExecutor.readCode(EvilExecutor.java:19)
  at ch.frankel.blog.runtimecompile.EvilExecutor.doEvil(EvilExecutor.java:47)
  at ch.frankel.blog.runtimecompile.EvilExecutor.main(EvilExecutor.java:56)

結論

JVM提供了很多功能。與任何工具一起使用時,無論好壞,它們都可以使用。每個人都有責任確保自己的JVM的安全,在敏感領域——銀行業、軍事等領域,情況更是如此。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章