/src/kilim/WeavingClassLoader.java

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