public static void demoJailbreak() throws Exception {
ClassLoader parent = Thread.currentThread().getContextClassLoader();
String jre = System.getProperty("java.home");
String jdk = jre.endsWith("/jre") ? jre.substring(0,jre.length()-3) : jre;
JavaCompiler broken, byPath=null, compiler;
try {
byPath = (JavaCompiler) loadByPath(
"com.sun.tools.javac.api.JavacTool",
"file:" + jdk + "lib/tools.jar",
parent
);
}
catch (Exception ex) {}
compiler = ToolProvider.getSystemJavaCompiler();
broken = (JavaCompiler) jailbreak(compiler.getClass(),parent);
System.out.println("jailbreak : " + broken);
System.out.println("loadByPath: " + byPath);
}
/**
* create a new instance of a class
* using a new url classloader with a specified parent
* and the same resource path as the original class instance
*/
public static Object jailbreak(Class klass,ClassLoader parent) throws Exception {
String cname = klass.getName();
String path = getPathForClass(cname,klass.getClassLoader());
Object dup = loadByPath(cname,path,parent);
return dup;
}
/** return the path at which an original classloader finds a class at */
public static String getPathForClass(String cname,ClassLoader orig)
throws Exception {
String rname = cname.replace(".","/") + ".class";
java.net.URL url = orig.getResource(rname);
String full = url.getPath();
// need to split the jar url on an exclamation mark per:
// https://docs.oracle.com/javase/7/docs/api/java/net/JarURLConnection.html
String path = full.split("!",0)[0];
return path;
}
/**
* create a new instance of a class by name
* from a new classloader using a file:/path/to/jar style path
* and the designated parent
*/
public static Object loadByPath(String cname,String path,ClassLoader parent)
throws Exception {
java.net.URL url2 = new java.net.URL(path);
java.net.URL [] urls = new java.net.URL [] {url2};
ClassLoader cl = new java.net.URLClassLoader(urls,parent);
Class klass = cl.loadClass(cname);
return klass.newInstance();
}
all static methods, no maven dependencies and shouldn't require importing anything, so cut and paste should work. note: this example will only work if you have a jdk installed (the compiler isn't in the jre), and the byPath version will only work if the jre path is "jdk_path/jre". that said, the jailbreak and loadByPath will work fine anywhere
Tuesday, January 30, 2018
load a java class with a different parent classloader
in the process of debugging issues with an incorrect classpath for the compiler api when run inside maven, i wanted to be able to create a duplicate instance of an existing object (that had been produced by a factory) by loading the class using a custom classloader with a different parent. it didn't fix the classpath problem, so i also tried loading directly by the path that i found, to no greater success
my solution is somewhere between kludgy and elegant, so i wanted to publish it in case some other sucker goes down a similar road less traveled (and it doesn't deserve it's own repository), so here it is:
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment