Home | History | Annotate | Download | only in rt
      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 
     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      */
     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 
    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 
    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 
    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     {
    319         PoolEntry (final int baosCapacity, final int bufSize)
    320         {
    321             m_baos = new ByteArrayOStream (baosCapacity);
    322             m_buf = new byte [bufSize];
    323         }
    324 
    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      */
    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 
    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      */
    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      */
    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      */
    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