• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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