/src/kilim/WeavingClassLoader.java
Java | 294 lines | 216 code | 35 blank | 43 comment | 53 complexity | 3cd501be1d0a435ea1fb1365923b9ec7 MD5 | raw file
1// copyright 2016 nqzero, 2014 sriram srinivasan - offered under the terms of the MIT License 2 3package kilim; 4 5import java.io.ByteArrayInputStream; 6import java.io.ByteArrayOutputStream; 7import java.io.DataInputStream; 8import java.io.File; 9import java.io.IOException; 10import java.io.InputStream; 11import java.lang.invoke.MethodHandle; 12import java.lang.invoke.MethodHandles; 13import java.lang.invoke.MethodType; 14import java.lang.reflect.Method; 15import java.net.URL; 16import java.net.URLClassLoader; 17import java.security.CodeSigner; 18import java.security.CodeSource; 19import java.security.ProtectionDomain; 20import java.util.ArrayList; 21import java.util.HashMap; 22 23import kilim.analysis.ClassInfo; 24import kilim.analysis.ClassWeaver; 25import kilim.analysis.FileLister; 26import kilim.analysis.KilimContext; 27import kilim.mirrors.CachedClassMirrors; 28import kilim.tools.Agent; 29import kilim.tools.Weaver; 30 31// TODO: this and related real-time-weaving classes could be moved to the analysis package 32// allowing the ant kilim-runtime.jar (see build.xml) to be smaller 33 34/** 35 * Classloader that loads classes from the classpath spec given by the system property 36 * "kilim.class.path" and weaves them dynamically. 37 */ 38public class WeavingClassLoader extends KilimClassLoader { 39 public static final String KILIM_CLASSPATH = "kilim.class.path"; 40 Weaver weaver; 41 42 URLClassLoader proxy; 43 44 public static byte [] readFully(InputStream is) { 45 try { 46 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 47 int num; 48 byte[] data = new byte[1 << 12]; 49 while ((num = is.read( data, 0, data.length )) != -1) 50 buffer.write(data, 0, num); 51 buffer.flush(); 52 return buffer.toByteArray(); 53 } 54 catch (IOException ex) { return null; } 55 } 56 57 ClassLoader pcl; 58 static ClassLoader platform; 59 static { 60 ClassLoader last, cl = WeavingClassLoader.class.getClassLoader(); 61 for (last = cl; cl != null; cl = cl.getParent()) 62 last = cl; 63 platform = last; 64 } 65 66 public InputStream getResourceAsStream(String cname) { 67 if (cname==null || !cname.endsWith(".class") || proxy(cname)) 68 return super.getResourceAsStream(cname); 69 String name = unmakeResourceName(cname); 70 InputStream is = getByteStream(pcl,name,cname,false); 71 72 if (is==null) return null; 73 74 byte [] code = null; 75 ClassWeaver cw = weaver.weave(is); 76 for (ClassInfo ci : cw.getClassInfos()) 77 if (ci.className.equals(name)) code = ci.bytes; 78 if (code==null) code = cw.classFlow.code; 79 return code==null ? null : new ByteArrayInputStream(code); 80 } 81 82 public static URL [] getURLs(String [] classPaths) { 83 ArrayList<URL> urls = new ArrayList<URL>(); 84 for (String name : classPaths) { 85 name = name.trim(); 86 if (name.equals("")) continue; 87 try { urls.add(new File(name).toURI().toURL()); } 88 catch (IOException ioe) { 89 // System.err.println( "'" + name + "' does not exist. See property " + 90 // KILIM_CLASSPATH); 91 } 92 } 93 94 URL [] paths = urls.toArray(new URL[0]); 95 return paths; 96 } 97 98 99 public WeavingClassLoader() { 100 this(null,getProxy()); 101 } 102 private static URLClassLoader getProxy() { 103 String classPath = System.getProperty(KILIM_CLASSPATH, ""); 104 String[] classPaths = classPath.split(":"); 105 URL [] paths = getURLs(classPaths); 106 return paths.length > 0 ? new URLClassLoader(paths) : null; 107 } 108 public WeavingClassLoader(ClassLoader loader,URLClassLoader $proxy) { 109 proxy = $proxy; 110 pcl = proxy != null 111 ? proxy 112 : loader != null ? loader:getClass().getClassLoader(); 113 114 CachedClassMirrors ccm = new CachedClassMirrors(pcl); 115 KilimContext kc = new KilimContext(ccm); 116 weaver = new Weaver(kc); 117 } 118 /** run static method className.method(args) using reflection and this WeavingClassLoader */ 119 public void run(String className,String method,String ... args) throws Exception { 120 Class<?> mainClass = loadClass(className); 121 Method mainMethod = mainClass.getMethod(method, new Class[]{String[].class}); 122 mainMethod.invoke(null,new Object[] {args}); 123 } 124 125 /** 126 * replace the exclude filter which determines whether a named class should be defined 127 * definitively by this loader or instead delegated to the parent loader. 128 * this is sometimes necessary to work with classes that may have been previously loaded, 129 * eg because they were used as a java agent 130 * @param exclude the filter to run 131 * @return 132 */ 133 public WeavingClassLoader exclude(Excludable exclude) { checker = exclude; return this; } 134 public static interface Excludable { 135 /** 136 * check whether the named class should be loaded by the parent loader 137 * @param name the name of the class to check 138 * @return true if this class loader should delegate to the parent loader 139 */ 140 boolean check(String name); 141 } 142 private Excludable checker; 143 144 145 public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 146 Class klass = null; 147 try { klass = platform.loadClass(name); } catch (ClassNotFoundException ex) {} 148 if (klass==null) 149 klass = findLoadedClass(name); 150 if (klass==null & (checker != null && checker.check(name))) 151 klass = pcl.loadClass(name); 152 if (klass==null) synchronized (this) { 153 klass = findLoadedClass(name); 154 if (klass==null) 155 klass = findClass( name ); 156 } 157 if (resolve) resolveClass( klass ); 158 return klass; 159 } 160 private boolean proxy(String cname) { 161 return proxy != null && proxy.findResource(cname) == null; 162 } 163 164 private Class defineAll(String name,ClassWeaver cw) { 165 Class ret = null; 166 for (ClassInfo ci : cw.getClassInfos()) { 167 if (findLoadedClass(ci.className)==null) { 168 Class<?> c = define(ci.className, ci.bytes); 169 if (ci.className.equals(name)) ret = c; 170 else if (ci.className.startsWith("kilim.S")) resolveClass(c); 171 } 172 } 173 return ret; 174 } 175 176 public static InputStream getByteStream(ClassLoader cl,String name,String cname,boolean system) { 177 InputStream is = cl.getResourceAsStream( cname ); 178 if (is==null && system) is = ClassLoader.getSystemResourceAsStream( cname ); 179 if (is==null & Agent.map != null) { 180 // force the nominal class loader to load the bytecode so that the agent sees it 181 try { cl.loadClass(name); } 182 catch (Exception ex) {} 183 byte [] bytes = Agent.map.get(cname); 184 if (bytes != null) is = new ByteArrayInputStream(bytes); 185 } 186 return is; 187 } 188 189 /** 190 * load the bytecode for a class of a given name from the classpath and weave it 191 * @param name the fully qualified class name 192 * @return the weaver 193 */ 194 public ClassWeaver weaveClass(String name) { 195 String cname = makeResourceName(name); 196 InputStream is = getByteStream(pcl,name,cname,true); 197 ClassWeaver cw = weaver.weave(is); 198 return cw; 199 } 200 protected Class<?> findClass(String name) throws ClassNotFoundException { 201 String cname = makeResourceName(name); 202 203 InputStream is = getByteStream(pcl,name,cname,true); 204 ClassWeaver cw; 205 byte [] code; 206 207 if (is==null) {} 208 else if (proxy(cname)) { 209 if ((code=readFully(is)) != null) 210 return define(name,code); 211 } 212 else if ((cw = weaver.weave(is)) != null) { 213 Class<?> ret = defineAll(name,cw); 214 return ret==null ? define(name, cw.classFlow.code) : ret; 215 } 216 throw new ClassNotFoundException(); 217 } 218 219 private final HashMap<URL, ProtectionDomain> cache = new HashMap<URL, ProtectionDomain>(); 220 private ProtectionDomain get(String name) { 221 URL url = url(name); 222 if (url==null) return null; 223 224 ProtectionDomain pd = null; 225 synchronized (cache) { 226 pd = cache.get(url); 227 if (pd == null) { 228 CodeSource cs = new CodeSource(url,(CodeSigner []) null); 229 pd = new ProtectionDomain(cs, null, this, null); 230 cache.put(url, pd); 231 } 232 } 233 return pd; 234 } 235 236 public Class<?> define(String name,byte [] code) { 237 CachedClassMirrors.ClassMirror cm = null; 238 return defineClass(name, code, 0, code.length, get(name)); 239 } 240 241 /** 242 * convert a fully qualified class name to a resource name. Note: the Class and ClassLoader 243 * javadocs don't explicitly specify the string formats used for the various methods, so 244 * this conversion is potentially fragile 245 * @param name as returned by Class.getName 246 * @return the name in a format suitable for use with the various ClassLoader.getResource methods 247 */ 248 // https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html#res_names 249 public static String makeResourceName(String name) { return name.replace('.','/') + ".class"; } 250 public static String unmakeResourceName(String cname) { return cname.replace(".class","").replace('/','.'); } 251 252 /** 253 * read bytecode for the named class from a source classloader 254 * @param loader the classloader to get the bytecode from, or null for the current classloader 255 * @param name the internal name for the class as would be passed to loadClass 256 * @return a new instance, or null if the bytecode is not found 257 */ 258 public static byte [] findCode(ClassLoader loader,String name) { 259 if (loader==null) loader = WeavingClassLoader.class.getClassLoader(); 260 String cname = makeResourceName(name); 261 InputStream is = getByteStream(loader,name,cname,true); 262 if (is != null) 263 return readFully(is); 264 return null; 265 } 266 public URL url(String name) { 267 String cname = makeResourceName(name); 268 URL url = pcl.getResource( cname ), ret = null; 269 if (url==null) url = ClassLoader.getSystemResource( cname ); 270 if (url==null) return null; 271 String path = url.getPath(); 272 273 boolean matches = path.endsWith(cname); 274 assert matches : "url code source doesn't match expectation: " + name + ", " + path; 275 if (! matches) return null; 276 277 try { 278 ret = new URL(url,path.replace(cname,"")); 279 } 280 catch (Exception ex) {} 281 return ret; 282 } 283 284 285 286 public static byte[] readFully(FileLister.Entry fe) throws IOException { 287 DataInputStream in = new DataInputStream(fe.getInputStream()); 288 byte[] contents = new byte[(int)fe.getSize()]; 289 in.readFully(contents); 290 in.close(); 291 return contents; 292 } 293 294}