1 /*
2  Copyright (c) 2010, 2021, Oracle and/or its affiliates.
3  All rights reserved. Use is subject to license terms.
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License, version 2.0,
7  as published by the Free Software Foundation.
8 
9  This program is also distributed with certain software (including
10  but not limited to OpenSSL) that is licensed under separate terms,
11  as designated in a particular file or component or in included license
12  documentation.  The authors of MySQL hereby grant you an additional
13  permission to link the program and your derivative works with the
14  separately licensed software that they have included with MySQL.
15 
16  This program is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  GNU General Public License, version 2.0, for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program; if not, write to the Free Software
23  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
24 */
25 /*
26  * MyLoadUnloadTest.java
27  */
28 
29 package test;
30 
31 import java.lang.ref.WeakReference;
32 
33 import java.lang.reflect.Method;
34 import java.lang.reflect.InvocationTargetException;
35 
36 import java.io.PrintWriter;
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.IOException;
40 import java.io.FileNotFoundException;
41 
42 import java.net.URL;
43 import java.net.URI;
44 import java.net.MalformedURLException;
45 import java.net.URISyntaxException;
46 import java.net.URLClassLoader;
47 
48 import java.nio.ByteBuffer;
49 import java.nio.channels.FileChannel;
50 
51 /**
52  * A test for loading, calling into, and unloading a native library.
53  *
54  * Under the JNI specification, a native library is loaded by the class loader
55  * of that class which called the method System.loadLibrary(String).  In a
56  * Java VM, a library cannot be loaded into more than one class loader
57  * (since JDK 1.2, to preserve the separation of name-spaces).  A library is
58  * unloaded when the class loader containing the library is garbage collected.
59  *
60  * Therefore, the task of loading of the native library has to be carried
61  * out by newly created class loader instances that are separate from the
62  * loader of this test class.  This implies that this test class must not
63  * statically refer to the code calling into the native library, for this
64  * would load the native library by this class loader when running this test,
65  * and any subsequent attempt to load the library by another class loader
66  * instance would fail (with an UnsatisfiedLinkError).
67  *
68  * Hence, this class tests the loading and unloading of a native library by
69  * executing the following cycle at least twice:
70  * <ol>
71  * <li> create a test class loader instance,
72  * <li> using reflection, instruct the test class loader to load the a
73  *      native library and run Java code calling into the native library,
74  * <li> clear any (strong) references to the test class loader
75  * <li> invoke the system's garbage collector to finalize the test class
76  *      loader and unload the library.
77  * </ol>
78  */
79 public class MyLoadUnloadTest {
80 
81     /**
82      *  The stream to write messages to.
83      */
84     static protected final PrintWriter out = new PrintWriter(System.out, true);
85 
86     /**
87      *  The stream to write error messages to.
88      */
89     static protected final PrintWriter err = new PrintWriter(System.err, true);
90 
91     // ensure that asserts are enabled
92     static {
93         boolean assertsEnabled = false;
94         assert assertsEnabled = true; // intentional side effect
95         if (!assertsEnabled) {
96             throw new RuntimeException("Asserts must be enabled for this test to be effective!");
97         }
98     }
99 
100     static final String pprefixes_prop
101         = "com.mysql.jtie.test.MyLoadUnloadTest.target_package_prefixes";
102     static final String cname_prop
103         = "com.mysql.jtie.test.MyLoadUnloadTest.target_class_name";
104     static final String mname_prop
105         = "com.mysql.jtie.test.MyLoadUnloadTest.target_method_name";
106 
107     /**
108      * A weak reference to a class loader loading the native library.
109      */
110     static protected WeakReference<ClassLoader> cl = null;
111 
112     /**
113      *  Shortcut to the Runtime.
114      */
115     static private final Runtime rt = Runtime.getRuntime();
116 
117     /**
118      * A ClassLoader that loads classes from certain packages by itself
119      * not delegating loading of these to the parent class loader.
120      */
121     // this loader is only used for debugging this test
122     static class SelfishFileClassLoader extends ClassLoader {
123 
SelfishFileClassLoader()124         public SelfishFileClassLoader() {
125             out.println("--> SelfishFileClassLoader()");
126             out.println("    this = " + this);
127             out.println("    getParent() = " + getParent());
128             out.println("<-- SelfishFileClassLoader()");
129         }
130 
getClassData(String name)131         private ByteBuffer getClassData(String name)
132             throws ClassNotFoundException {
133             out.println("--> SelfishFileClassLoader.getClassData(String)");
134 
135             // for simplicity of code, only support loading classes from files;
136             // an alternative approach to collect all bytes using
137             //   InputStream ClassLoader.getSystemResourceAsStream(String)
138             //   ReadableByteChannel Channels.newChannel(InputStream)
139             //   int ReadableByteChannel.read(ByteBuffer)
140             // requires multiple copying within nested loops, since the total
141             // number of bytes to be read is not known in advance.
142 
143             String rname = name.replace('.', '/') + ".class";
144 
145             // locate the class data
146             final URL url = ClassLoader.getSystemResource(rname);
147             if (url == null) {
148                 final String msg = ("no definition of the class "
149                                     + rname + " could be found.");
150                 throw new ClassNotFoundException(msg);
151             }
152             out.println("    url = " + url);
153 
154             // convert into a URI
155             final URI uri;
156             try {
157                 uri = url.toURI();
158             } catch (URISyntaxException ex) {
159                 final String msg = ("the URL " + url + " is not formatted"
160                                     + "strictly according to RFC2396 and "
161                                     + "cannot be converted to a URI.");
162                 throw new ClassNotFoundException(msg, ex);
163             }
164 
165             // convert into a pathname
166             final File f;
167             try {
168                 f = new File(uri);
169             } catch (IllegalArgumentException ex) {
170                 final String msg = ("the system-dependent URI " + uri
171                                     + " cannot be converted into a pathname.");
172                 throw new ClassNotFoundException(msg, ex);
173             }
174 
175             // check accessibility
176             if (!f.canRead()) {
177                 final String msg = ("cannot read file " + f);
178                 throw new ClassNotFoundException(msg);
179             }
180             out.println("    can read file " + f);
181             final long nn = f.length();
182             out.println("    f.length = " + nn);
183 
184             // open a FileInputStream
185             final FileInputStream fis;
186             try {
187                 fis = new FileInputStream(f);
188             } catch (FileNotFoundException ex) {
189                 final String msg = ("the file " + f
190                                     + " does not exist, is a directory, or"
191                                     + " cannot be opened for reading.");
192                 throw new ClassNotFoundException(msg, ex);
193             }
194 
195             // get the stream's FileChannel
196             final FileChannel fc = fis.getChannel();
197             assert (fc != null);
198             out.println("    fc = " + fc);
199 
200             // allocate a ByteBuffer and read file's content
201             final ByteBuffer bb;
202             try {
203                 // not worth mapping file as ByteBuffer
204                 //   final MappedByteBuffer mbb
205                 //       = fc.map(MapMode.READ_ONLY, 0, fc.size());
206                 // unclear performance gain but platform-dependent behaviour
207 
208                 // retrieve the file's size
209                 final long n;
210                 try {
211                     n = fc.size();
212                 } catch (IOException ex) {
213                     final String msg = ("cannot get size of file " + f);
214                     throw new ClassNotFoundException(msg, ex);
215                 }
216                 assert (n == nn);
217 
218                 // allocate a ByteBuffer
219                 if (n > Integer.MAX_VALUE) {
220                     final String msg = ("size of file " + f
221                                         + " larger than Integer.MAX_VALUE.");
222                     throw new ClassFormatError(msg);
223                 }
224                 bb = ByteBuffer.allocateDirect((int)n);
225 
226                 // read file's content into a ByteBuffer
227                 try {
228                     int k;
229                     while ((k = fc.read(bb)) > 0) {
230                         out.println("    read " + k + " bytes");
231                     }
232                 } catch (IOException ex) {
233                     final String msg = ("cannot read file " + f);
234                     throw new ClassNotFoundException(msg, ex);
235                 }
236                 assert (bb.remaining() == 0);
237                 bb.rewind();
238             } finally {
239                 try {
240                     fc.close();
241                 } catch (IOException ex) {
242                     final String msg = ("cannot close FileChannel " + fc);
243                     throw new ClassNotFoundException(msg, ex);
244                 }
245                 try {
246                     fis.close();
247                 } catch (IOException ex) {
248                     final String msg = ("cannot close FileInputStream " + fis);
249                     throw new ClassNotFoundException(msg, ex);
250                 }
251             }
252 
253             out.println("<-- SelfishFileClassLoader.getClassData(String)");
254             return bb;
255         }
256 
257         // Under the Java ClassLoader delegation model, subclasses are
258         // encouraged to override findClass(), whose default implementation
259         // throws a ClassNotFoundException, rather than loadClass().
260         // However, for the purpose of ensuring that certain classes are only
261         // loaded by child class loaders, it is not sufficient to override
262         // findClass(), which is invoked by loadClass() only after it has
263         // delegated loading to the parent class loader with no success.
loadClass(String name, boolean resolve)264         protected Class<?> loadClass(String name, boolean resolve)
265             throws ClassNotFoundException {
266             out.println("--> SelfishFileClassLoader.loadClass(String, boolean)");
267 
268             // check if the class has already been loaded
269             Class<?> cls = findLoadedClass(name);
270             if (cls != null) {
271                 out.println("    already loaded " + cls);
272                 return cls;
273             }
274 
275             //cls = super.findClass(name);
276             //cls = super.loadClass(name, resolve);
277 
278             // load test's classes by this ClassLoader, delegate others
279             if (name.startsWith("test")
280                 || name.startsWith("myjapi")
281                 || name.startsWith("jtie")) {
282                 out.println("    self:    loading " + name + " ...");
283                 final ByteBuffer bb = getClassData(name);
284                 cls = defineClass(name, bb, null);
285             } else {
286                 out.println("    parent:  loading " + name + " ...");
287                 cls = getParent().loadClass(name);
288             }
289             assert (cls != null);
290 
291             out.println("    ... loaded " + cls
292                         + " <" + cls.getClassLoader() + ">");
293 
294             // link class if requested
295             if (resolve) {
296                 out.println("    linking " + cls + " ...");
297                 resolveClass(cls);
298                 out.println("    ... linked " + cls);
299             }
300 
301             out.println("<-- SelfishFileClassLoader.loadClass(String, boolean)");
302             return cls;
303         }
304     }
305 
306     /**
307      * A ClassLoader selectively blocking the delegation of the loading of
308      * classes to parent class loaders.
309      */
310     static class FilterClassLoader extends ClassLoader {
311 
312         // list of package prefixes not to be loaded by parent class loaders
313         protected final String[] prefixes;
314 
FilterClassLoader(String[] prefixes)315         public FilterClassLoader(String[] prefixes) {
316             out.println("--> FilterClassLoader()");
317             out.println("    this = " + this);
318             out.println("    getParent() = " + getParent());
319             out.println("    prefixes[] = {");
320             for (int i = 0; i < prefixes.length; i++) {
321                 out.println("        \"" + prefixes[i] + "\"");
322             }
323             out.println("    }");
324             this.prefixes = prefixes;
325             out.println("<-- FilterClassLoader()");
326         }
327 
328         // should never be called, since only invoked by loadClass()
findClass(String name)329         protected Class<?> findClass(String name)
330             throws ClassNotFoundException {
331             assert (false) : "should never be called";
332             throw new ClassNotFoundException();
333         }
334 
335         // selectively load classes by parent class loaders
loadClass(String name, boolean resolve)336         protected Class<?> loadClass(String name, boolean resolve)
337             throws ClassNotFoundException {
338             out.println("--> FilterClassLoader.loadClass(String, boolean)");
339             Class<?> cls = null;
340 
341             // this loader does not find and load any classes by itself
342             assert (findLoadedClass(name) == null);
343 
344             // give up on loading if class matches any of the prefixes
345             for (int i = prefixes.length - 1; i >= 0; i--) {
346                 if (name.startsWith(prefixes[i])) {
347                     out.println("    redirect loading " + name);
348                     out.println("<<< FilterClassLoader.loadClass(String, boolean)");
349                     throw new ClassNotFoundException();
350                 }
351             }
352 
353             // delegate loading of class to parent class loaders
354             out.println("    delegate loading " + name);
355             cls = getParent().loadClass(name);
356             assert (cls != null);
357             //out.println("    ... loaded " + cls
358             //            + " <" + cls.getClassLoader() + ">");
359 
360             // link class if requested
361             if (resolve) {
362                 out.println("    linking " + cls + " ...");
363                 resolveClass(cls);
364                 out.println("    ... linked " + cls);
365             }
366 
367             out.println("<-- FilterClassLoader.loadClass(String, boolean)");
368             return cls;
369         }
370     }
371 
372     /**
373      * A URLClassLoader with tracing capabilities for debugging.
374      */
375     static class MyURLClassLoader extends URLClassLoader {
376 
MyURLClassLoader(URL[] urls, ClassLoader parent)377         public MyURLClassLoader(URL[] urls, ClassLoader parent) {
378             super(urls, parent);
379             out.println("--> MyURLClassLoader(URL[], ClassLoader)");
380             out.println("    this = " + this);
381             out.println("    getParent() = " + getParent());
382             out.println("    urls[] = {");
383             for (int i = 0; i < urls.length; i++) {
384                 out.println("        " + urls[i]);
385             }
386             out.println("    }");
387             out.println("<-- MyURLClassLoader(URL[], ClassLoader)");
388         }
389 
findClass(String name)390         protected Class<?> findClass(String name)
391             throws ClassNotFoundException {
392             out.println("--> MyURLClassFinder.findClass(String, boolean)");
393             out.println("    finding " + name + " ...");
394             Class<?> cls = super.findClass(name);
395             out.println("    ... found " + cls
396                         + " <" + cls.getClassLoader() + ">");
397             out.println("<-- MyURLClassFinder.findClass(String, boolean)");
398             return cls;
399         }
400 
loadClass(String name, boolean resolve)401         protected Class<?> loadClass(String name, boolean resolve)
402             throws ClassNotFoundException {
403             out.println("--> MyURLClassLoader.loadClass(String, boolean)");
404             out.println("    loading " + name + " ...");
405             Class<?> cls = super.loadClass(name, resolve);
406             out.println("    ... loaded " + cls
407                         + " <" + cls.getClassLoader() + ">");
408             out.println("<-- MyURLClassLoader.loadClass(String, boolean)");
409             return cls;
410         }
411     }
412 
413     /**
414      * Attempts to run the JVM's Garbage Collector.
415      */
classPathURLs()416     static private URL[] classPathURLs() throws MalformedURLException {
417         final String cp = System.getProperty("java.class.path");
418         assert (cp != null) : ("classpath = '" + cp + "'");
419         final String[] s = cp.split(File.pathSeparator);
420         final URL[] urls = new URL[s.length];
421         for (int i = 0; i < s.length; i++) {
422             urls[i] = new File(s[i]).toURI().toURL();
423         }
424         return urls;
425     }
426 
test0()427     static public void test0()
428         throws ClassNotFoundException, NoSuchMethodException,
429         IllegalAccessException, InvocationTargetException,
430         MalformedURLException, InstantiationException {
431         out.flush();
432         out.println("--> MyLoadUnloadTest.test0()");
433 
434         out.println();
435         out.println("    MyLoadUnloadTest.class.getClassLoader() = "
436                     + MyLoadUnloadTest.class.getClassLoader());
437         out.println("    ClassLoader.getSystemClassLoader() = "
438                     +  ClassLoader.getSystemClassLoader());
439 
440         // read properties specifying the test to run
441         final String pprefixes = System.getProperty(pprefixes_prop,
442                                                     "test.,myjapi.");
443         final String[] pprefix = pprefixes.split(",");
444         final String cname = System.getProperty(cname_prop, "test.MyJapiTest");
445         final String mname = System.getProperty(mname_prop, "test");
446         out.println();
447         out.println("    settings:");
448         out.println("        pprefixes = '" + pprefixes + "'");
449         out.println("        cname = '" + cname + "'");
450         out.println("        mname = '" + mname + "'");
451 
452         // set up class loaders
453         out.println();
454         out.println("    create FilterClassLoader ...");
455         final ClassLoader fcl = new FilterClassLoader(pprefix);
456         out.println("    ... created " + fcl);
457 
458         out.println();
459         out.println("    create URLClassLoader ...");
460         final URL[] urls = classPathURLs();
461         //final ClassLoader ucl = new MyURLClassLoader(urls, fcl);
462         final ClassLoader ucl = new URLClassLoader(urls, fcl);
463         out.println("    ... created " + ucl);
464 
465         // store class loader in a global but weak reference
466         cl = new WeakReference<ClassLoader>(ucl);
467 
468         // run test
469         out.println();
470         out.println("    load class ...");
471         Class<?> cls = ucl.loadClass(cname);
472         out.println("    ... loaded " + cls
473                     + " <" + cls.getClassLoader() + ">");
474 
475         out.println();
476         out.println("    get method: " + mname + " ...");
477         Method m = cls.getMethod("test");
478         out.println("    ... got method: " + m);
479 
480         out.println();
481         out.println("    create instance: ...");
482         Object o = cls.newInstance();
483         out.println("    ... created instance: " + o);
484 
485         out.println();
486         out.println("    invoke method: " + m + " ...");
487         m.invoke(o);
488         out.println("    ... invoked method: " + m);
489 
490         out.println();
491         out.println("<-- MyLoadUnloadTest.test0()");
492         out.flush();
493     }
494 
495     /**
496      * Attempts to run the JVM's Garbage Collector.
497      */
gc()498     static private void gc() {
499         out.println("--> MyLoadUnloadTest.gc()");
500         out.println("    jvm mem: " + (rt.totalMemory() - rt.freeMemory())/1024
501                     + " KiB [" + rt.totalMemory()/1024 + " KiB]");
502         // empirically determined limit after which no further
503         // reduction in memory usage has been observed
504         final int nFullGCs = 100;
505         //final int nFullGCs = 10;
506         for (int i = 0; i < nFullGCs; i++) {
507             long oldfree;
508             long newfree = rt.freeMemory();
509             do {
510                 // help I/O appear in sync between Java/native
511                 synchronized (MyLoadUnloadTest.class) {
512                     oldfree = newfree;
513                     out.println("    rt.gc()");
514                     out.flush();
515                     err.flush();
516 
517                     rt.runFinalization();
518                     rt.gc();
519                     newfree = rt.freeMemory();
520                 }
521             } while (newfree > oldfree);
522         }
523         out.println("    jvm mem: " + (rt.totalMemory() - rt.freeMemory())/1024
524                     + " KiB [" + rt.totalMemory()/1024 + " KiB]");
525         out.println("<-- MyLoadUnloadTest.gc()");
526     }
527 
test()528     static public void test()
529         throws ClassNotFoundException, NoSuchMethodException,
530         IllegalAccessException, InvocationTargetException,
531         MalformedURLException, InstantiationException {
532         out.flush();
533         out.println("--> MyLoadUnloadTest.test()");
534 
535         for (int i = 0; i < 3; i++) {
536             // create a class loader, load native library, and run test
537             test0();
538 
539             // garbage collect class loader that loaded native library
540             gc();
541 
542             // if the class loader with the native library has not been
543             // garbage collected, for instance, due to a strong caching
544             // reference, the next test invocation will fail with, e.g.:
545             //   java.lang.UnsatisfiedLinkError: Native Library <libmyjapi>
546             //   already loaded in another classloader
547             if (cl.get() != null) {
548                 out.println("!!! the class loader with the native library"
549                             + " has not been garbage collected (for instance,"
550                             + " due to a strong caching reference)!");
551                 break;
552             }
553         }
554 
555         out.println();
556         out.println("<-- MyLoadUnloadTest.test()");
557         out.flush();
558     }
559 
main(String[] args)560     static public void main(String[] args)
561         throws Exception {
562         out.println("--> MyLoadUnloadTest.main()");
563 
564         test();
565 
566         // help I/O appear in sync between Java/native
567         synchronized (MyLoadUnloadTest.class) {
568             //try {
569             //    MyLoadUnloadTest.class.wait(1000);
570             //} catch (InterruptedException ex) {
571             //    // ignore
572             //}
573             out.flush();
574             err.flush();
575         }
576 
577         out.println();
578         out.println("<-- MyLoadUnloadTest.main()");
579     }
580 }
581