1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. 2 * 3 * This program and the accompanying materials are made available under 4 * the terms of the Common Public License v1.0 which accompanies this distribution, 5 * and is available at http://www.eclipse.org/legal/cpl-v10.html 6 * 7 * $Id: InstrClassLoader.java,v 1.1.1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $ 8 */ 9 package com.vladium.emma.rt; 10 11 import java.io.File; 12 import java.io.IOException; 13 import java.io.InputStream; 14 import java.io.PrintWriter; 15 import java.net.MalformedURLException; 16 import java.net.URL; 17 import java.net.URLClassLoader; 18 import java.security.CodeSource; 19 import java.security.cert.Certificate; 20 import java.util.Map; 21 22 import com.vladium.logging.Logger; 23 import com.vladium.util.ByteArrayOStream; 24 import com.vladium.util.asserts.$assert; 25 import com.vladium.emma.IAppConstants; 26 import com.vladium.emma.filter.IInclExclFilter; 27 28 // ---------------------------------------------------------------------------- 29 /** 30 * @author Vlad Roubtsov, (C) 2003 31 */ 32 public 33 final class InstrClassLoader extends URLClassLoader 34 { 35 // public: ................................................................ 36 37 // TODO: proper security [use PrivilegedAction as needed] 38 // TODO: [see above as well] need to keep track of res URLs to support [path] exclusion patterns 39 // TODO: improve error handling so it is clear when errors come from buggy instrumentation 40 41 42 public static final String PROPERTY_FORCED_DELEGATION_FILTER = "clsload.forced_delegation_filter"; 43 public static final String PROPERTY_THROUGH_DELEGATION_FILTER = "clsload.through_delegation_filter"; 44 45 InstrClassLoader(final ClassLoader parent, final File [] classpath, final IInclExclFilter forcedDelegationFilter, final IInclExclFilter throughDelegationFilter, final IClassLoadHook hook, final Map cache)46 public InstrClassLoader (final ClassLoader parent, final File [] classpath, 47 final IInclExclFilter forcedDelegationFilter, 48 final IInclExclFilter throughDelegationFilter, 49 final IClassLoadHook hook, final Map cache) 50 throws MalformedURLException 51 { 52 // setting ClassLoader.parent to null disables the standard delegation 53 // behavior in a few places, including URLClassLoader.getResource(): 54 55 super (filesToURLs (classpath), null); 56 57 // TODO: arg validation 58 59 m_hook = hook; 60 m_cache = cache; // can be null 61 62 m_forcedDelegationFilter = forcedDelegationFilter; 63 m_throughDelegationFilter = throughDelegationFilter; 64 65 m_parent = parent; 66 m_bufPool = new PoolEntry [BAOS_POOL_SIZE]; 67 68 m_log = Logger.getLogger (); 69 } 70 71 /** 72 * Overrides java.lang.ClassLoader.loadClass() to change the usual parent-child 73 * delegation rules just enough to be able to 'replace' the parent loader. This 74 * also has the effect of detecting 'system' classes without doing any class 75 * name-based matching. 76 */ loadClass(final String name, final boolean resolve)77 public synchronized final Class loadClass (final String name, final boolean resolve) 78 throws ClassNotFoundException 79 { 80 final boolean trace1 = m_log.atTRACE1 (); 81 82 if (trace1) m_log.trace1 ("loadClass", "(" + name + ", " + resolve + "): nest level " + m_nestLevel); 83 84 Class c = null; 85 86 // first, check if this class has already been defined by this classloader 87 // instance: 88 c = findLoadedClass (name); 89 90 if (c == null) 91 { 92 Class parentsVersion = null; 93 if (m_parent != null) 94 { 95 try 96 { 97 parentsVersion = m_parent.loadClass (name); // note: it is important that this does not init the class 98 99 if ((parentsVersion.getClassLoader () != m_parent) || 100 ((m_forcedDelegationFilter == null) || m_forcedDelegationFilter.included (name))) 101 { 102 // (a) m_parent itself decided to delegate: use parent's version 103 // (b) the class was on the forced delegation list: use parent's version 104 c = parentsVersion; 105 if (trace1) m_log.trace1 ("loadClass", "using parent's version for [" + name + "]"); 106 } 107 } 108 catch (ClassNotFoundException cnfe) 109 { 110 // if the class was on the forced delegation list, error out: 111 if ((m_forcedDelegationFilter == null) || m_forcedDelegationFilter.included (name)) 112 throw cnfe; 113 } 114 } 115 116 if (c == null) 117 { 118 try 119 { 120 // either (a) m_parent was null or (b) it could not load 'c' 121 // or (c) it will define 'c' itself if allowed to. In any 122 // of these cases I attempt to define my own version: 123 c = findClass (name); 124 } 125 catch (ClassNotFoundException cnfe) 126 { 127 // this is a difficult design point unless I resurrect the -lx option 128 // and document how to use it [which will confuse most users anyway] 129 130 // another alternative would be to see if parent's version is included by 131 // the filter and print a warning; still, it does not help with JAXP etc 132 133 if (parentsVersion != null) 134 { 135 final boolean delegate = (m_throughDelegationFilter == null) || m_throughDelegationFilter.included (name); 136 137 if (delegate) 138 { 139 c = parentsVersion; 140 if (trace1) m_log.trace1 ("loadClass", "[delegation filter] using parent's version for [" + name + "]"); 141 } 142 else 143 throw cnfe; 144 } 145 else 146 throw cnfe; 147 } 148 } 149 } 150 151 if (c == null) throw new ClassNotFoundException (name); 152 153 if (resolve) resolveClass (c); // this never happens in J2SE JVMs 154 return c; 155 } 156 157 // TODO: remove this in the release build 158 getResource(final String name)159 public final URL getResource (final String name) 160 { 161 final boolean trace1 = m_log.atTRACE1 (); 162 163 if (trace1) m_log.trace1 ("getResource", "(" + name + "): nest level " + m_nestLevel); 164 165 final URL result = super.getResource (name); 166 if (trace1 && (result != null)) m_log.trace1 ("loadClass", "[" + name + "] found in " + result); 167 168 return result; 169 } 170 171 // protected: ............................................................. 172 173 findClass(final String name)174 protected final Class findClass (final String name) 175 throws ClassNotFoundException 176 { 177 final boolean trace1 = m_log.atTRACE1 (); 178 179 if (trace1) m_log.trace1 ("findClass", "(" + name + "): nest level " + m_nestLevel); 180 181 final boolean useClassCache = (m_cache != null); 182 final ClassPathCacheEntry entry = useClassCache ? (ClassPathCacheEntry) m_cache.remove (name) : null; 183 184 byte [] bytes; 185 int length; 186 URL classURL = null; 187 188 if (entry != null) // cache hit 189 { 190 ++ m_cacheHits; 191 192 // used cached class def bytes, no need to repeat disk I/O: 193 194 try 195 { 196 classURL = new URL (entry.m_srcURL); 197 } 198 catch (MalformedURLException murle) // this should never happen 199 { 200 if ($assert.ENABLED) 201 { 202 murle.printStackTrace (System.out); 203 } 204 } 205 206 PoolEntry buf = null; 207 try 208 { 209 buf = acquirePoolEntry (); 210 final ByteArrayOStream baos = buf.m_baos; // reset() has been called on this 211 212 // the original class definition: 213 bytes = entry.m_bytes; 214 length = bytes.length; 215 216 if ((m_hook != null) && m_hook.processClassDef (name, bytes, length, baos)) // note: this can overwrite 'bytes' 217 { 218 // the instrumented class definition: 219 bytes = baos.getByteArray (); 220 length = baos.size (); 221 222 if (trace1) m_log.trace1 ("findClass", "defining [cached] instrumented [" + name + "] {" + length + " bytes }"); 223 } 224 else 225 { 226 if (trace1) m_log.trace1 ("findClass", "defining [cached] [" + name + "] {" + length + " bytes }"); 227 } 228 229 return defineClass (name, bytes, length, classURL); 230 } 231 catch (IOException ioe) 232 { 233 throw new ClassNotFoundException (name); 234 } 235 finally 236 { 237 if (buf != null) releasePoolEntry (buf); 238 } 239 } 240 else // cache miss 241 { 242 if (useClassCache) ++ m_cacheMisses; 243 244 // .class files are not guaranteed to be loadable as resources; 245 // but if Sun's code does it... 246 final String classResource = name.replace ('.', '/') + ".class"; 247 248 // even thought normal delegation is disabled, this will find bootstrap classes: 249 classURL = getResource (classResource); // important to hook into URLClassLoader's overload of this so that Class-Path manifest attributes are processed etc 250 251 if (trace1 && (classURL != null)) m_log.trace1 ("findClass", "[" + name + "] found in " + classURL); 252 253 if (classURL == null) 254 throw new ClassNotFoundException (name); 255 else 256 { 257 InputStream in = null; 258 PoolEntry buf = null; 259 try 260 { 261 in = classURL.openStream (); 262 263 buf = acquirePoolEntry (); 264 final ByteArrayOStream baos = buf.m_baos; // reset() has been called on this 265 266 readFully (in, baos, buf.m_buf); 267 in.close (); // don't keep the file handle across reentrant calls 268 in = null; 269 270 // the original class definition: 271 bytes = baos.getByteArray (); 272 length = baos.size (); 273 274 baos.reset (); // reuse this for processClassDef below 275 276 if ((m_hook != null) && m_hook.processClassDef (name, bytes, length, baos)) // note: this can overwrite 'bytes' 277 { 278 // the instrumented class definition: 279 bytes = baos.getByteArray (); 280 length = baos.size (); 281 282 if (trace1) m_log.trace1 ("findClass", "defining instrumented [" + name + "] {" + length + " bytes }"); 283 } 284 else 285 { 286 if (trace1) m_log.trace1 ("findClass", "defining [" + name + "] {" + length + " bytes }"); 287 } 288 289 return defineClass (name, bytes, length, classURL); 290 } 291 catch (IOException ioe) 292 { 293 throw new ClassNotFoundException (name); 294 } 295 finally 296 { 297 if (buf != null) releasePoolEntry (buf); 298 if (in != null) try { in.close (); } catch (Exception ignore) {} 299 } 300 } 301 } 302 } 303 debugDump(final PrintWriter out)304 public void debugDump (final PrintWriter out) 305 { 306 if (out != null) 307 { 308 out.println (this + ": " + m_cacheHits + " class cache hits, " + m_cacheMisses + " misses"); 309 } 310 } 311 312 // package: ............................................................... 313 314 // private: ............................................................... 315 316 317 private static final class PoolEntry 318 { PoolEntry(final int baosCapacity, final int bufSize)319 PoolEntry (final int baosCapacity, final int bufSize) 320 { 321 m_baos = new ByteArrayOStream (baosCapacity); 322 m_buf = new byte [bufSize]; 323 } 324 trim(final int baosCapacity, final int baosMaxCapacity)325 void trim (final int baosCapacity, final int baosMaxCapacity) 326 { 327 if (m_baos.capacity () > baosMaxCapacity) 328 { 329 m_baos = new ByteArrayOStream (baosCapacity); 330 } 331 } 332 333 ByteArrayOStream m_baos; 334 final byte [] m_buf; 335 336 } // end of nested class 337 338 339 /* 340 * 'srcURL' may be null 341 */ defineClass(final String className, final byte [] bytes, final int length, final URL srcURL)342 private Class defineClass (final String className, final byte [] bytes, final int length, final URL srcURL) 343 { 344 // support ProtectionDomains with non-null class source URLs: 345 // [however, disable anything related to sealing or signing] 346 347 final CodeSource csrc = new CodeSource (srcURL, (Certificate[])null); 348 349 // allow getPackage() to return non-null on the class we are about to 350 // define (however, don't bother emulating the original manifest info since 351 // we may be altering manifest content anyway): 352 353 final int lastDot = className.lastIndexOf ('.'); 354 if (lastDot >= 0) 355 { 356 final String packageName = className.substring (0, lastDot); 357 358 final Package pkg = getPackage (packageName); 359 if (pkg == null) 360 { 361 definePackage (packageName, 362 IAppConstants.APP_NAME, IAppConstants.APP_VERSION, IAppConstants.APP_COPYRIGHT, 363 IAppConstants.APP_NAME, IAppConstants.APP_VERSION, IAppConstants.APP_COPYRIGHT, 364 srcURL); 365 } 366 } 367 368 return defineClass (className, bytes, 0, length, csrc); 369 } 370 371 filesToURLs(final File [] classpath)372 private static URL [] filesToURLs (final File [] classpath) 373 throws MalformedURLException 374 { 375 if ((classpath == null) || (classpath.length == 0)) 376 return EMPTY_URL_ARRAY; 377 378 final URL [] result = new URL [classpath.length]; 379 380 for (int f = 0; f < result.length ; ++ f) 381 { 382 result [f] = classpath [f].toURL (); // note: this does proper dir encoding 383 } 384 385 return result; 386 } 387 388 /** 389 * Reads the entire contents of a given stream into a flat byte array. 390 */ readFully(final InputStream in, final ByteArrayOStream out, final byte [] buf)391 private static void readFully (final InputStream in, final ByteArrayOStream out, final byte [] buf) 392 throws IOException 393 { 394 for (int read; (read = in.read (buf)) >= 0; ) 395 { 396 out.write (buf, 0, read); 397 } 398 } 399 400 /* 401 * not MT-safe; must be called from loadClass() only 402 */ acquirePoolEntry()403 private PoolEntry acquirePoolEntry () 404 { 405 PoolEntry result; 406 407 if (m_nestLevel >= BAOS_POOL_SIZE) 408 { 409 result = new PoolEntry (BAOS_INIT_SIZE, BAOS_INIT_SIZE); 410 } 411 else 412 { 413 result = m_bufPool [m_nestLevel]; 414 if (result == null) 415 { 416 result = new PoolEntry (BAOS_INIT_SIZE, BAOS_INIT_SIZE); 417 m_bufPool [m_nestLevel] = result; 418 } 419 else 420 { 421 result.m_baos.reset (); 422 } 423 } 424 425 ++ m_nestLevel; 426 427 return result; 428 } 429 430 /* 431 * not MT-safe; must be called from loadClass() only 432 */ releasePoolEntry(final PoolEntry buf)433 private void releasePoolEntry (final PoolEntry buf) 434 { 435 if (-- m_nestLevel < BAOS_POOL_SIZE) 436 { 437 buf.trim (BAOS_INIT_SIZE, BAOS_MAX_SIZE); 438 } 439 } 440 441 442 private final ClassLoader m_parent; 443 444 private final IInclExclFilter m_forcedDelegationFilter; 445 private final IInclExclFilter m_throughDelegationFilter; 446 447 private final Map /* classJavaName:String -> ClassPathCacheEntry */ m_cache; // can be null 448 private final IClassLoadHook m_hook; 449 private final PoolEntry [] m_bufPool; 450 451 private final Logger m_log; // a loader instance is used concurrently but cached its log config at construction time 452 453 private int m_nestLevel; 454 455 private int m_cacheHits, m_cacheMisses; 456 457 private static final int BAOS_INIT_SIZE = 32 * 1024; 458 private static final int BAOS_MAX_SIZE = 1024 * 1024; 459 private static final int BAOS_POOL_SIZE = 8; 460 private static final URL [] EMPTY_URL_ARRAY = new URL [0]; 461 462 } // end of class 463 // ---------------------------------------------------------------------------- 464