Java使得它可以在运行时...... 任何 Java代码编译Java代码。
编译的入口点是ToolProvider
类。 从其Javadoc:
提供用于定位工具提供者(例如,编译器的提供者)的方法。 此类补充了ServiceLoader的功能。
从10年前发布的1.6版开始,此类就可以在Java中使用,但似乎已被很大程度上忽略。
编码
这是一个允许的片段:
publicclassEvilExecutor{
privateStringreadCode(StringsourcePath)throwsFileNotFoundException{
InputStreamstream=newFileInputStream(sourcePath);
Stringseparator=System.getProperty("line.separator");
BufferedReaderreader=newBufferedReader(newInputStreamReader(stream));
returnreader.lines().collect(Collectors.joining(separator));
}
privatePathsaveSource(Stringsource)throwsIOException{
StringtmpProperty=System.getProperty("java.io.tmpdir");
PathsourcePath=Paths.get(tmpProperty,"Harmless.java");
Files.write(sourcePath,source.getBytes(UTF_8));
returnsourcePath;
}
privatePathcompileSource(PathjavaFile){
JavaCompilercompiler=ToolProvider.getSystemJavaCompiler();
compiler.run(null,null,null,javaFile.toFile().getAbsolutePath());
returnjavaFile.getParent().resolve("Harmless.class");
}
privatevoidrunClass(PathjavaClass)
throwsMalformedURLException,ClassNotFoundException,IllegalAccessException,InstantiationException{
URLclassUrl=javaClass.getParent().toFile().toURI().toURL();
URLClassLoaderclassLoader=URLClassLoader.newInstance(newURL[]{classUrl});
Class<?>clazz=Class.forName("Harmless",true,classLoader);
clazz.newInstance();
}
publicvoiddoEvil(StringsourcePath)throwsException{
Stringsource=readCode(sourcePath);
PathjavaFile=saveSource(source);
PathclassFile=compileSource(javaFile);
runClass(classFile);
}
publicstaticvoidmain(String...args)throwsException{
newEvilExecutor().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
执行上面的命令行将产生以下结果:
[email protected] 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负责,在银行,军事等敏感领域则要加倍。
翻译自: https://blog.frankel.ch/compilation-java-code-on-the-fly/